{"owner":"modelcontextprotocol","repo":"python-sdk","hasSkills":true,"hasMcp":true,"mcpConfig":{"mcpServers":{"python-sdk":{"command":"npx","args":["-y","@modelcontextprotocol/server-python-sdk"]}}},"found":["AGENTS.md"],"skills":{"AGENTS.md":"# Development Guidelines\n\n## Branching Model\n\n- `main` is the current stable line (v2); releases are cut from it (see\n  `RELEASE.md`).\n- Removing or replacing an API must be intentional, and what shipped in 2.x\n  is public surface. Adding a replacement API or `@deprecated` shim is\n  likewise a deliberate design choice, not bolted on for free.\n- Changes that break code written against v1 (including those softened by a\n  backwards-compatibility shim) must be documented in `docs/migration.md`.\n- `v1.x` is the maintenance branch for the previous major. Backport PRs\n  target it and use a `[v1.x]` title prefix; only critical bug fixes and\n  security fixes land there.\n- `README.md` documents v2. The v1 README lives on the `v1.x` branch.\n\n## Package Management\n\n- ONLY use uv, NEVER pip\n- Installation: `uv add <package>`. Exception: the root project's runtime\n  dependencies are dynamic (the published `mcp` wheel exact-pins `mcp-types`),\n  so `uv add` cannot edit them — add the requirement to\n  `[tool.hatch.metadata.hooks.uv-dynamic-versioning].dependencies` in\n  `pyproject.toml` by hand, then run `uv lock`. Dependency groups, extras, and\n  the example packages still take plain `uv add`.\n- Running tools: `uv run --frozen <tool>`. Always pass `--frozen` so uv doesn't\n  rewrite `uv.lock` as a side effect.\n- Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run\n  against a specific interpreter (CI covers 3.10–3.14).\n- Upgrading: `uv lock --upgrade-package <package>`\n- FORBIDDEN: `uv pip install`, `@latest` syntax\n- Don't raise dependency floors for CVEs alone. The `>=` constraint already\n  lets users upgrade. Only raise a floor when the SDK needs functionality from\n  the newer version, and don't add SDK code to work around a dependency's\n  vulnerability. See Kludex/uvicorn#2643 and python-sdk #1552 for reasoning.\n\n## Code Quality\n\n- Type hints required for all code\n- Public APIs must have docstrings. When a public API raises exceptions a\n  caller would reasonably catch, document them in a `Raises:` section. Don't\n  list exceptions from argument validation or programmer error.\n- `src/mcp/__init__.py` defines the public API surface via `__all__`. Adding a\n  symbol there is a deliberate API decision, not a convenience re-export.\n- IMPORTANT: All imports go at the top of the file — inline imports hide\n  dependencies and obscure circular-import bugs. Only exception: when a\n  top-level import genuinely can't work (lazy-loading optional deps, or\n  tests that re-import a module).\n\n## Testing\n\n- When writing or reviewing tests, conform to `.claude/skills/test-quality/SKILL.md`\n  — it defines the bar for naming, abstraction level, assertions, and determinism.\n- Framework: `uv run --frozen pytest`\n- Async testing: use anyio, not asyncio\n- Do not use `Test` prefixed classes — write plain top-level `test_*` functions.\n  Legacy files still contain `Test*` classes; do NOT follow that pattern for new\n  tests even when adding to such a file.\n- IMPORTANT: Tests should be fast and deterministic. Prefer in-memory async execution;\n  reach for threads only when necessary, and subprocesses only as a last resort.\n- For end-to-end behavior, an in-memory `Client(server)` is usually the\n  cleanest approach (see `tests/client/test_client.py` for the canonical\n  pattern). For narrower changes, testing the function directly is fine. Use\n  judgment.\n- Test files mirror the source tree: `src/mcp/client/stdio.py` →\n  `tests/client/test_stdio.py`. Add tests to the existing file for that module.\n- Avoid `anyio.sleep()` with a fixed duration to wait for async operations. Instead:\n  - Use `anyio.Event` — set it in the callback/handler, `await event.wait()` in the test\n  - For stream messages, use `await stream.receive()` instead of `sleep()` + `receive_nowait()`\n  - Exception: `sleep()` is appropriate when testing time-based features (e.g., timeouts)\n- Wrap indefinite waits (`event.wait()`, `stream.receive()`) in `anyio.fail_after(5)` to prevent hangs\n- Pytest is configured with `filterwarnings = [\"error\"]`, so warnings fail\n  tests. Don't silence warnings from your own code; fix the underlying cause.\n  Scoped `ignore::` entries for upstream libraries are acceptable in\n  `pyproject.toml` with a comment explaining why.\n- New features from the 2026-07-28 spec must have a matching test in the\n  [conformance suite](https://github.com/modelcontextprotocol/conformance)\n  that passes against this SDK (CI runs it via\n  `.github/workflows/conformance.yml`). If no matching test exists, stop and\n  tell the user so they can raise an issue on the conformance repo.\n\n### Coverage\n\nCI requires 100% (`fail_under = 100`, `branch = true`).\n\n- Full check: `./scripts/test` (~23s). Runs coverage + `strict-no-cover` on the\n  default Python. Not identical to CI: CI runs 3.10–3.14 × {ubuntu, windows}\n  × {locked, lowest-direct}, and some branch-coverage quirks only surface on\n  specific matrix entries.\n- Targeted check while iterating (~4s, deterministic):\n\n  ```bash\n  uv run --frozen coverage erase\n  uv run --frozen coverage run -m pytest tests/path/test_foo.py\n  uv run --frozen coverage combine\n  uv run --frozen coverage report --include='src/mcp/path/foo.py' --fail-under=0\n  # UV_FROZEN=1 propagates --frozen to the uv subprocess strict-no-cover spawns\n  UV_FROZEN=1 uv run --frozen strict-no-cover\n  ```\n\n  Partial runs can't hit 100% (coverage tracks `tests/` too), so `--fail-under=0`\n  and `--include` scope the report. `strict-no-cover` has no false positives on\n  partial runs — if your new test executes a line marked `# pragma: no cover`,\n  even a single-file run catches it.\n\nAvoid adding new `# pragma: no cover`, `# type: ignore`, or `# noqa` comments.\nIn tests, use `assert isinstance(x, T)` to narrow types instead of\n`# type: ignore`. In library code (`src/`), a `# pragma: no cover` needs very\ngood reasoning — it usually means a test is missing. Audit before pushing:\n\n```bash\ngit diff origin/main... | grep -E '^\\+.*(pragma|type: ignore|noqa)'\n```\n\nWhat the existing pragmas mean:\n\n- `# pragma: no cover` — line is never executed. CI's `strict-no-cover` (skipped\n  on Windows runners) fails if it IS executed. When your test starts covering\n  such a line, remove the pragma.\n- `# pragma: lax no cover` — excluded from coverage but not checked by\n  `strict-no-cover`. Use for lines covered on some platforms/versions but not\n  others.\n- `# pragma: no branch` — excludes branch arcs only. coverage.py misreports the\n  `->exit` arc for nested `async with` on Python 3.11+ (worse on 3.14/Windows).\n\n## Breaking Changes\n\nWhen making breaking changes, document them in `docs/migration.md` — including\nchanges softened by a backwards-compatibility shim. Include:\n\n- What changed\n- Why it changed\n- How to migrate existing code\n\nSearch for related sections in the migration guide and group related changes together\nrather than adding new standalone sections.\n\n## Documentation\n\nWhen a change affects public API or user-visible behaviour, update the relevant\npage(s) under `docs/` in the same PR. Docs are organised by the `nav:` sections\nin `mkdocs.yml` (Get started, Servers, Inside your handler, Running your server,\nClients, Advanced), not by the on-disk directory names. Find the page covering\nthe feature you touched in `mkdocs.yml` rather than adding a new one.\n\n## Formatting & Type Checking\n\n- Format: `uv run --frozen ruff format .`\n- Lint: `uv run --frozen ruff check . --fix`\n- Type check: `uv run --frozen pyright`\n- Pre-commit runs all of the above plus markdownlint, a `uv.lock` consistency\n  check, and README checks — see `.pre-commit-config.yaml`\n\n## Exception Handling\n\n- **Always use `logger.exception()` instead of `logger.error()` when catching exceptions**\n  - Don't include the exception in the message: `logger.exception(\"Failed\")` not `logger.exception(f\"Failed: {e}\")`\n- **Catch specific exceptions** where possible:\n  - File ops: `except (OSError, PermissionError):`\n  - JSON: `except json.JSONDecodeError:`\n  - Network: `except (ConnectionError, TimeoutError):`\n- **FORBIDDEN** `except Exception:` - unless in top-level handlers\n"},"files":{"AGENTS.md":"# Development Guidelines\n\n## Branching Model\n\n- `main` is the current stable line (v2); releases are cut from it (see\n  `RELEASE.md`).\n- Removing or replacing an API must be intentional, and what shipped in 2.x\n  is public surface. Adding a replacement API or `@deprecated` shim is\n  likewise a deliberate design choice, not bolted on for free.\n- Changes that break code written against v1 (including those softened by a\n  backwards-compatibility shim) must be documented in `docs/migration.md`.\n- `v1.x` is the maintenance branch for the previous major. Backport PRs\n  target it and use a `[v1.x]` title prefix; only critical bug fixes and\n  security fixes land there.\n- `README.md` documents v2. The v1 README lives on the `v1.x` branch.\n\n## Package Management\n\n- ONLY use uv, NEVER pip\n- Installation: `uv add <package>`. Exception: the root project's runtime\n  dependencies are dynamic (the published `mcp` wheel exact-pins `mcp-types`),\n  so `uv add` cannot edit them — add the requirement to\n  `[tool.hatch.metadata.hooks.uv-dynamic-versioning].dependencies` in\n  `pyproject.toml` by hand, then run `uv lock`. Dependency groups, extras, and\n  the example packages still take plain `uv add`.\n- Running tools: `uv run --frozen <tool>`. Always pass `--frozen` so uv doesn't\n  rewrite `uv.lock` as a side effect.\n- Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run\n  against a specific interpreter (CI covers 3.10–3.14).\n- Upgrading: `uv lock --upgrade-package <package>`\n- FORBIDDEN: `uv pip install`, `@latest` syntax\n- Don't raise dependency floors for CVEs alone. The `>=` constraint already\n  lets users upgrade. Only raise a floor when the SDK needs functionality from\n  the newer version, and don't add SDK code to work around a dependency's\n  vulnerability. See Kludex/uvicorn#2643 and python-sdk #1552 for reasoning.\n\n## Code Quality\n\n- Type hints required for all code\n- Public APIs must have docstrings. When a public API raises exceptions a\n  caller would reasonably catch, document them in a `Raises:` section. Don't\n  list exceptions from argument validation or programmer error.\n- `src/mcp/__init__.py` defines the public API surface via `__all__`. Adding a\n  symbol there is a deliberate API decision, not a convenience re-export.\n- IMPORTANT: All imports go at the top of the file — inline imports hide\n  dependencies and obscure circular-import bugs. Only exception: when a\n  top-level import genuinely can't work (lazy-loading optional deps, or\n  tests that re-import a module).\n\n## Testing\n\n- When writing or reviewing tests, conform to `.claude/skills/test-quality/SKILL.md`\n  — it defines the bar for naming, abstraction level, assertions, and determinism.\n- Framework: `uv run --frozen pytest`\n- Async testing: use anyio, not asyncio\n- Do not use `Test` prefixed classes — write plain top-level `test_*` functions.\n  Legacy files still contain `Test*` classes; do NOT follow that pattern for new\n  tests even when adding to such a file.\n- IMPORTANT: Tests should be fast and deterministic. Prefer in-memory async execution;\n  reach for threads only when necessary, and subprocesses only as a last resort.\n- For end-to-end behavior, an in-memory `Client(server)` is usually the\n  cleanest approach (see `tests/client/test_client.py` for the canonical\n  pattern). For narrower changes, testing the function directly is fine. Use\n  judgment.\n- Test files mirror the source tree: `src/mcp/client/stdio.py` →\n  `tests/client/test_stdio.py`. Add tests to the existing file for that module.\n- Avoid `anyio.sleep()` with a fixed duration to wait for async operations. Instead:\n  - Use `anyio.Event` — set it in the callback/handler, `await event.wait()` in the test\n  - For stream messages, use `await stream.receive()` instead of `sleep()` + `receive_nowait()`\n  - Exception: `sleep()` is appropriate when testing time-based features (e.g., timeouts)\n- Wrap indefinite waits (`event.wait()`, `stream.receive()`) in `anyio.fail_after(5)` to prevent hangs\n- Pytest is configured with `filterwarnings = [\"error\"]`, so warnings fail\n  tests. Don't silence warnings from your own code; fix the underlying cause.\n  Scoped `ignore::` entries for upstream libraries are acceptable in\n  `pyproject.toml` with a comment explaining why.\n- New features from the 2026-07-28 spec must have a matching test in the\n  [conformance suite](https://github.com/modelcontextprotocol/conformance)\n  that passes against this SDK (CI runs it via\n  `.github/workflows/conformance.yml`). If no matching test exists, stop and\n  tell the user so they can raise an issue on the conformance repo.\n\n### Coverage\n\nCI requires 100% (`fail_under = 100`, `branch = true`).\n\n- Full check: `./scripts/test` (~23s). Runs coverage + `strict-no-cover` on the\n  default Python. Not identical to CI: CI runs 3.10–3.14 × {ubuntu, windows}\n  × {locked, lowest-direct}, and some branch-coverage quirks only surface on\n  specific matrix entries.\n- Targeted check while iterating (~4s, deterministic):\n\n  ```bash\n  uv run --frozen coverage erase\n  uv run --frozen coverage run -m pytest tests/path/test_foo.py\n  uv run --frozen coverage combine\n  uv run --frozen coverage report --include='src/mcp/path/foo.py' --fail-under=0\n  # UV_FROZEN=1 propagates --frozen to the uv subprocess strict-no-cover spawns\n  UV_FROZEN=1 uv run --frozen strict-no-cover\n  ```\n\n  Partial runs can't hit 100% (coverage tracks `tests/` too), so `--fail-under=0`\n  and `--include` scope the report. `strict-no-cover` has no false positives on\n  partial runs — if your new test executes a line marked `# pragma: no cover`,\n  even a single-file run catches it.\n\nAvoid adding new `# pragma: no cover`, `# type: ignore`, or `# noqa` comments.\nIn tests, use `assert isinstance(x, T)` to narrow types instead of\n`# type: ignore`. In library code (`src/`), a `# pragma: no cover` needs very\ngood reasoning — it usually means a test is missing. Audit before pushing:\n\n```bash\ngit diff origin/main... | grep -E '^\\+.*(pragma|type: ignore|noqa)'\n```\n\nWhat the existing pragmas mean:\n\n- `# pragma: no cover` — line is never executed. CI's `strict-no-cover` (skipped\n  on Windows runners) fails if it IS executed. When your test starts covering\n  such a line, remove the pragma.\n- `# pragma: lax no cover` — excluded from coverage but not checked by\n  `strict-no-cover`. Use for lines covered on some platforms/versions but not\n  others.\n- `# pragma: no branch` — excludes branch arcs only. coverage.py misreports the\n  `->exit` arc for nested `async with` on Python 3.11+ (worse on 3.14/Windows).\n\n## Breaking Changes\n\nWhen making breaking changes, document them in `docs/migration.md` — including\nchanges softened by a backwards-compatibility shim. Include:\n\n- What changed\n- Why it changed\n- How to migrate existing code\n\nSearch for related sections in the migration guide and group related changes together\nrather than adding new standalone sections.\n\n## Documentation\n\nWhen a change affects public API or user-visible behaviour, update the relevant\npage(s) under `docs/` in the same PR. Docs are organised by the `nav:` sections\nin `mkdocs.yml` (Get started, Servers, Inside your handler, Running your server,\nClients, Advanced), not by the on-disk directory names. Find the page covering\nthe feature you touched in `mkdocs.yml` rather than adding a new one.\n\n## Formatting & Type Checking\n\n- Format: `uv run --frozen ruff format .`\n- Lint: `uv run --frozen ruff check . --fix`\n- Type check: `uv run --frozen pyright`\n- Pre-commit runs all of the above plus markdownlint, a `uv.lock` consistency\n  check, and README checks — see `.pre-commit-config.yaml`\n\n## Exception Handling\n\n- **Always use `logger.exception()` instead of `logger.error()` when catching exceptions**\n  - Don't include the exception in the message: `logger.exception(\"Failed\")` not `logger.exception(f\"Failed: {e}\")`\n- **Catch specific exceptions** where possible:\n  - File ops: `except (OSError, PermissionError):`\n  - JSON: `except json.JSONDecodeError:`\n  - Network: `except (ConnectionError, TimeoutError):`\n- **FORBIDDEN** `except Exception:` - unless in top-level handlers\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# Development Guidelines\n\n## Branching Model\n\n- `main` is the current stable line (v2); releases are cut from it (see\n  `RELEASE.md`).\n- Removing or replacing an API must be intentional, and what shipped in 2.x\n  is public surface. Adding a replacement API or `@deprecated` shim is\n  likewise a deliberate design choice, not bolted on for free.\n- Changes that break code written against v1 (including those softened by a\n  backwards-compatibility shim) must be documented in `docs/migration.md`.\n- `v1.x` is the maintenance branch for the previous major. Backport PRs\n  target it and use a `[v1.x]` title prefix; only critical bug fixes and\n  security fixes land there.\n- `README.md` documents v2. The v1 README lives on the `v1.x` branch.\n\n## Package Management\n\n- ONLY use uv, NEVER pip\n- Installation: `uv add <package>`. Exception: the root project's runtime\n  dependencies are dynamic (the published `mcp` wheel exact-pins `mcp-types`),\n  so `uv add` cannot edit them — add the requirement to\n  `[tool.hatch.metadata.hooks.uv-dynamic-versioning].dependencies` in\n  `pyproject.toml` by hand, then run `uv lock`. Dependency groups, extras, and\n  the example packages still take plain `uv add`.\n- Running tools: `uv run --frozen <tool>`. Always pass `--frozen` so uv doesn't\n  rewrite `uv.lock` as a side effect.\n- Cross-version testing: `uv run --frozen --python 3.10 pytest ...` to run\n  against a specific interpreter (CI covers 3.10–3.14).\n- Upgrading: `uv lock --upgrade-package <package>`\n- FORBIDDEN: `uv pip install`, `@latest` syntax\n- Don't raise dependency floors for CVEs alone. The `>=` constraint already\n  lets users upgrade. Only raise a floor when the SDK needs functionality from\n  the newer version, and don't add SDK code to work around a dependency's\n  vulnerability. See Kludex/uvicorn#2643 and python-sdk #1552 for reasoning.\n\n## Code Quality\n\n- Type hints required for all code\n- Public APIs must have docstrings. When a public API raises exceptions a\n  caller would reasonably catch, document them in a `Raises:` section. Don't\n  list exceptions from argument validation or programmer error.\n- `src/mcp/__init__.py` defines the public API surface via `__all__`. Adding a\n  symbol there is a deliberate API decision, not a convenience re-export.\n- IMPORTANT: All imports go at the top of the file — inline imports hide\n  dependencies and obscure circular-import bugs. Only exception: when a\n  top-level import genuinely can't work (lazy-loading optional deps, or\n  tests that re-import a module).\n\n## Testing\n\n- When writing or reviewing tests, conform to `.claude/skills/test-quality/SKILL.md`\n  — it defines the bar for naming, abstraction level, assertions, and determinism.\n- Framework: `uv run --frozen pytest`\n- Async testing: use anyio, not asyncio\n- Do not use `Test` prefixed classes — write plain top-level `test_*` functions.\n  Legacy files still contain `Test*` classes; do NOT follow that pattern for new\n  tests even when adding to such a file.\n- IMPORTANT: Tests should be fast and deterministic. Prefer in-memory async execution;\n  reach for threads only when necessary, and subprocesses only as a last resort.\n- For end-to-end behavior, an in-memory `Client(server)` is usually the\n  cleanest approach (see `tests/client/test_client.py` for the canonical\n  pattern). For narrower changes, testing the function directly is fine. Use\n  judgment.\n- Test files mirror the source tree: `src/mcp/client/stdio.py` →\n  `tests/client/test_stdio.py`. Add tests to the existing file for that module.\n- Avoid `anyio.sleep()` with a fixed duration to wait for async operations. Instead:\n  - Use `anyio.Event` — set it in the callback/handler, `await event.wait()` in the test\n  - For stream messages, use `await stream.receive()` instead of `sleep()` + `receive_nowait()`\n  - Exception: `sleep()` is appropriate when testing time-based features (e.g., timeouts)\n- Wrap indefinite waits (`event.wait()`, `stream.receive()`) in `anyio.fail_after(5)` to prevent hangs\n- Pytest is configured with `filterwarnings = [\"error\"]`, so warnings fail\n  tests. Don't silence warnings from your own code; fix the underlying cause.\n  Scoped `ignore::` entries for upstream libraries are acceptable in\n  `pyproject.toml` with a comment explaining why.\n- New features from the 2026-07-28 spec must have a matching test in the\n  [conformance suite](https://github.com/modelcontextprotocol/conformance)\n  that passes against this SDK (CI runs it via\n  `.github/workflows/conformance.yml`). If no matching test exists, stop and\n  tell the user so they can raise an issue on the conformance repo.\n\n### Coverage\n\nCI requires 100% (`fail_under = 100`, `branch = true`).\n\n- Full check: `./scripts/test` (~23s). Runs coverage + `strict-no-cover` on the\n  default Python. Not identical to CI: CI runs 3.10–3.14 × {ubuntu, windows}\n  × {locked, lowest-direct}, and some branch-coverage quirks only surface on\n  specific matrix entries.\n- Targeted check while iterating (~4s, deterministic):\n\n  ```bash\n  uv run --frozen coverage erase\n  uv run --frozen coverage run -m pytest tests/path/test_foo.py\n  uv run --frozen coverage combine\n  uv run --frozen coverage report --include='src/mcp/path/foo.py' --fail-under=0\n  # UV_FROZEN=1 propagates --frozen to the uv subprocess strict-no-cover spawns\n  UV_FROZEN=1 uv run --frozen strict-no-cover\n  ```\n\n  Partial runs can't hit 100% (coverage tracks `tests/` too), so `--fail-under=0`\n  and `--include` scope the report. `strict-no-cover` has no false positives on\n  partial runs — if your new test executes a line marked `# pragma: no cover`,\n  even a single-file run catches it.\n\nAvoid adding new `# pragma: no cover`, `# type: ignore`, or `# noqa` comments.\nIn tests, use `assert isinstance(x, T)` to narrow types instead of\n`# type: ignore`. In library code (`src/`), a `# pragma: no cover` needs very\ngood reasoning — it usually means a test is missing. Audit before pushing:\n\n```bash\ngit diff origin/main... | grep -E '^\\+.*(pragma|type: ignore|noqa)'\n```\n\nWhat the existing pragmas mean:\n\n- `# pragma: no cover` — line is never executed. CI's `strict-no-cover` (skipped\n  on Windows runners) fails if it IS executed. When your test starts covering\n  such a line, remove the pragma.\n- `# pragma: lax no cover` — excluded from coverage but not checked by\n  `strict-no-cover`. Use for lines covered on some platforms/versions but not\n  others.\n- `# pragma: no branch` — excludes branch arcs only. coverage.py misreports the\n  `->exit` arc for nested `async with` on Python 3.11+ (worse on 3.14/Windows).\n\n## Breaking Changes\n\nWhen making breaking changes, document them in `docs/migration.md` — including\nchanges softened by a backwards-compatibility shim. Include:\n\n- What changed\n- Why it changed\n- How to migrate existing code\n\nSearch for related sections in the migration guide and group related changes together\nrather than adding new standalone sections.\n\n## Documentation\n\nWhen a change affects public API or user-visible behaviour, update the relevant\npage(s) under `docs/` in the same PR. Docs are organised by the `nav:` sections\nin `mkdocs.yml` (Get started, Servers, Inside your handler, Running your server,\nClients, Advanced), not by the on-disk directory names. Find the page covering\nthe feature you touched in `mkdocs.yml` rather than adding a new one.\n\n## Formatting & Type Checking\n\n- Format: `uv run --frozen ruff format .`\n- Lint: `uv run --frozen ruff check . --fix`\n- Type check: `uv run --frozen pyright`\n- Pre-commit runs all of the above plus markdownlint, a `uv.lock` consistency\n  check, and README checks — see `.pre-commit-config.yaml`\n\n## Exception Handling\n\n- **Always use `logger.exception()` instead of `logger.error()` when catching exceptions**\n  - Don't include the exception in the message: `logger.exception(\"Failed\")` not `logger.exception(f\"Failed: {e}\")`\n- **Catch specific exceptions** where possible:\n  - File ops: `except (OSError, PermissionError):`\n  - JSON: `except json.JSONDecodeError:`\n  - Network: `except (ConnectionError, TimeoutError):`\n- **FORBIDDEN** `except Exception:` - unless in top-level handlers\n","category":"root","tokens":2028}]}