stackdoctor 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/.github/workflows/release.yml +1 -1
  2. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/PKG-INFO +14 -8
  3. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/README.md +13 -7
  4. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/pyproject.toml +1 -1
  5. stackdoctor-0.1.2/stackdoctor/__init__.py +9 -0
  6. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/server.py +30 -1
  7. stackdoctor-0.1.2/tests/test_cli.py +47 -0
  8. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/uv.lock +1 -1
  9. stackdoctor-0.1.0/stackdoctor/__init__.py +0 -0
  10. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/.gitattributes +0 -0
  11. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/.github/workflows/demo.yml +0 -0
  12. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/.github/workflows/tests.yml +0 -0
  13. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/.gitignore +0 -0
  14. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/DEMO.md +0 -0
  15. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/LICENSE +0 -0
  16. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/.env.example +0 -0
  17. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/app/Dockerfile +0 -0
  18. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/app/api.py +0 -0
  19. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/app/enqueue.py +0 -0
  20. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/app/requirements.txt +0 -0
  21. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/app/tasks.py +0 -0
  22. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/break_worker.ps1 +0 -0
  23. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/break_worker.sh +0 -0
  24. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/docker-compose.yml +0 -0
  25. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/e2e.py +0 -0
  26. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/lock_table.ps1 +0 -0
  27. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/lock_table.sh +0 -0
  28. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/postgres/init.sql +0 -0
  29. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/reset.ps1 +0 -0
  30. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/reset.sh +0 -0
  31. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/slow_query.ps1 +0 -0
  32. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/demo/slow_query.sh +0 -0
  33. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/docs/images/diagnosis.png +0 -0
  34. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/__main__.py +0 -0
  35. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/checks/__init__.py +0 -0
  36. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/checks/celery.py +0 -0
  37. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/checks/logs.py +0 -0
  38. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/checks/postgres.py +0 -0
  39. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/checks/redis.py +0 -0
  40. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/config.py +0 -0
  41. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/diagnose.py +0 -0
  42. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/stackdoctor/safety.py +0 -0
  43. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/tests/test_diagnose.py +0 -0
  44. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/tests/test_integration.py +0 -0
  45. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/tests/test_logs.py +0 -0
  46. {stackdoctor-0.1.0 → stackdoctor-0.1.2}/tests/test_safety.py +0 -0
@@ -1,7 +1,7 @@
1
1
  name: release
2
2
 
3
3
  # Publishes to PyPI with trusted publishing (OIDC), no API token.
4
- # Triggered by pushing a version tag that matches pyproject.toml, e.g. `git tag v0.1.0 && git push origin v0.1.0`.
4
+ # Triggered by pushing a version tag that matches pyproject.toml: git tag "v$(uv version --short)" && git push origin --tags
5
5
 
6
6
  on:
7
7
  push:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: stackdoctor
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Read-only MCP server that diagnoses a Python backend stack (Postgres, Celery, Redis, logs) with cross-system correlation.
5
5
  Project-URL: Homepage, https://github.com/lepri89/stackdoctor
6
6
  Project-URL: Issues, https://github.com/lepri89/stackdoctor/issues
@@ -20,6 +20,7 @@ Description-Content-Type: text/markdown
20
20
 
21
21
  [![tests](https://github.com/lepri89/stackdoctor/actions/workflows/tests.yml/badge.svg)](https://github.com/lepri89/stackdoctor/actions/workflows/tests.yml)
22
22
  [![demo](https://github.com/lepri89/stackdoctor/actions/workflows/demo.yml/badge.svg)](https://github.com/lepri89/stackdoctor/actions/workflows/demo.yml)
23
+ [![PyPI](https://img.shields.io/pypi/v/stackdoctor)](https://pypi.org/project/stackdoctor/)
23
24
  ![python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)
24
25
  ![license](https://img.shields.io/badge/license-MIT-green)
25
26
 
@@ -60,20 +61,23 @@ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS
60
61
  powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
61
62
  ```
62
63
 
63
- Then one command runs the server, with no other install step:
64
+ Then either run it straight from [PyPI](https://pypi.org/project/stackdoctor/) or install it once:
64
65
 
65
66
  ```sh
66
- uvx stackdoctor
67
+ uvx stackdoctor # runs the latest release, nothing to install
68
+ uv tool install stackdoctor # or install it; the command is then `stackdoctor`
67
69
  ```
68
70
 
69
- Until the package is on PyPI, run it from a checkout with `uvx --from /path/to/stackdoctor stackdoctor`,
70
- or from git with `uvx --from git+https://github.com/lepri89/stackdoctor stackdoctor`.
71
+ You don't normally run it by hand: your MCP client starts it (see [Configure](#configure)).
72
+ To check that it works, run `uvx stackdoctor --help`, which prints usage and the configuration
73
+ variables. To upgrade, use `uvx stackdoctor@latest` or `uv tool upgrade stackdoctor`.
71
74
 
72
75
  > **macOS 12 (Monterey):** `uvx stackdoctor` fails with `realpath: command not found`, because uv's
73
76
  > launcher script needs `realpath`, which only ships with macOS 13+. Use
74
77
  > `uvx --from stackdoctor python -m stackdoctor` instead. In client configs that means
75
- > `"args": ["--from", "stackdoctor", "python", "-m", "stackdoctor"]`. Alternatively, run
76
- > `uv tool install stackdoctor` once and use `stackdoctor` as the command.
78
+ > `"args": ["--from", "stackdoctor", "python", "-m", "stackdoctor"]`. Alternatively, use
79
+ > `uv tool install stackdoctor` and set the command to the full path of the installed tool,
80
+ > `/Users/<username>/.local/bin/stackdoctor`. Its launcher doesn't need `realpath`.
77
81
 
78
82
  ## Configure
79
83
 
@@ -317,7 +321,9 @@ in WSL2 and run the `.ps1` scripts from PowerShell.
317
321
  ## Development
318
322
 
319
323
  ```sh
324
+ git clone https://github.com/lepri89/stackdoctor && cd stackdoctor
320
325
  uv sync
326
+ uv run stackdoctor --help # run from the checkout
321
327
  uv run pytest # unit tests (safety layer, diagnose, logs)
322
328
  STACKDOCTOR_TEST_DATABASE_URL=postgresql://shop:shop@localhost:55432/shop \
323
329
  STACKDOCTOR_TEST_REDIS_URL=redis://localhost:56379/0 \
@@ -333,7 +339,7 @@ Releases go to PyPI from GitHub Actions with
333
339
  `lepri89`, repository `stackdoctor`, workflow `release.yml` and environment `pypi`. In GitHub, create
334
340
  an environment named `pypi` (Settings → Environments); adding yourself as a required reviewer is a good idea.
335
341
  2. Bump `version` in `pyproject.toml`, commit, then tag and push:
336
- `git tag v0.1.0 && git push origin v0.1.0`.
342
+ `git tag "v$(uv version --short)" && git push origin "v$(uv version --short)"`.
337
343
  The workflow checks that the tag matches the version, runs the tests, builds, and publishes.
338
344
 
339
345
  ## License
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![tests](https://github.com/lepri89/stackdoctor/actions/workflows/tests.yml/badge.svg)](https://github.com/lepri89/stackdoctor/actions/workflows/tests.yml)
4
4
  [![demo](https://github.com/lepri89/stackdoctor/actions/workflows/demo.yml/badge.svg)](https://github.com/lepri89/stackdoctor/actions/workflows/demo.yml)
5
+ [![PyPI](https://img.shields.io/pypi/v/stackdoctor)](https://pypi.org/project/stackdoctor/)
5
6
  ![python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)
6
7
  ![license](https://img.shields.io/badge/license-MIT-green)
7
8
 
@@ -42,20 +43,23 @@ curl -LsSf https://astral.sh/uv/install.sh | sh # macOS
42
43
  powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
43
44
  ```
44
45
 
45
- Then one command runs the server, with no other install step:
46
+ Then either run it straight from [PyPI](https://pypi.org/project/stackdoctor/) or install it once:
46
47
 
47
48
  ```sh
48
- uvx stackdoctor
49
+ uvx stackdoctor # runs the latest release, nothing to install
50
+ uv tool install stackdoctor # or install it; the command is then `stackdoctor`
49
51
  ```
50
52
 
51
- Until the package is on PyPI, run it from a checkout with `uvx --from /path/to/stackdoctor stackdoctor`,
52
- or from git with `uvx --from git+https://github.com/lepri89/stackdoctor stackdoctor`.
53
+ You don't normally run it by hand: your MCP client starts it (see [Configure](#configure)).
54
+ To check that it works, run `uvx stackdoctor --help`, which prints usage and the configuration
55
+ variables. To upgrade, use `uvx stackdoctor@latest` or `uv tool upgrade stackdoctor`.
53
56
 
54
57
  > **macOS 12 (Monterey):** `uvx stackdoctor` fails with `realpath: command not found`, because uv's
55
58
  > launcher script needs `realpath`, which only ships with macOS 13+. Use
56
59
  > `uvx --from stackdoctor python -m stackdoctor` instead. In client configs that means
57
- > `"args": ["--from", "stackdoctor", "python", "-m", "stackdoctor"]`. Alternatively, run
58
- > `uv tool install stackdoctor` once and use `stackdoctor` as the command.
60
+ > `"args": ["--from", "stackdoctor", "python", "-m", "stackdoctor"]`. Alternatively, use
61
+ > `uv tool install stackdoctor` and set the command to the full path of the installed tool,
62
+ > `/Users/<username>/.local/bin/stackdoctor`. Its launcher doesn't need `realpath`.
59
63
 
60
64
  ## Configure
61
65
 
@@ -299,7 +303,9 @@ in WSL2 and run the `.ps1` scripts from PowerShell.
299
303
  ## Development
300
304
 
301
305
  ```sh
306
+ git clone https://github.com/lepri89/stackdoctor && cd stackdoctor
302
307
  uv sync
308
+ uv run stackdoctor --help # run from the checkout
303
309
  uv run pytest # unit tests (safety layer, diagnose, logs)
304
310
  STACKDOCTOR_TEST_DATABASE_URL=postgresql://shop:shop@localhost:55432/shop \
305
311
  STACKDOCTOR_TEST_REDIS_URL=redis://localhost:56379/0 \
@@ -315,7 +321,7 @@ Releases go to PyPI from GitHub Actions with
315
321
  `lepri89`, repository `stackdoctor`, workflow `release.yml` and environment `pypi`. In GitHub, create
316
322
  an environment named `pypi` (Settings → Environments); adding yourself as a required reviewer is a good idea.
317
323
  2. Bump `version` in `pyproject.toml`, commit, then tag and push:
318
- `git tag v0.1.0 && git push origin v0.1.0`.
324
+ `git tag "v$(uv version --short)" && git push origin "v$(uv version --short)"`.
319
325
  The workflow checks that the tag matches the version, runs the tests, builds, and publishes.
320
326
 
321
327
  ## License
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stackdoctor"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "Read-only MCP server that diagnoses a Python backend stack (Postgres, Celery, Redis, logs) with cross-system correlation."
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -0,0 +1,9 @@
1
+ """stackdoctor: read-only MCP server that diagnoses a Python backend stack."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ # pyproject.toml is the single source of truth; this reads the installed package's metadata.
7
+ __version__ = version("stackdoctor")
8
+ except PackageNotFoundError: # running from a source tree that isn't installed
9
+ __version__ = "0.0.0+unknown"
@@ -2,6 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import argparse
5
6
  import functools
6
7
  import inspect
7
8
  import logging
@@ -10,6 +11,7 @@ import sys
10
11
  from mcp.server.mcpserver import MCPServer
11
12
  from mcp.types import ToolAnnotations
12
13
 
14
+ from . import __version__
13
15
  from . import diagnose as diag
14
16
  from .checks import celery, logs, postgres, redis
15
17
  from .config import get_config
@@ -154,7 +156,34 @@ def search_logs(pattern: str, since_minutes: int = 30, source: str | None = None
154
156
  return logs.search_logs(pattern, since_minutes, source)
155
157
 
156
158
 
157
- def main() -> None:
159
+ USAGE_EPILOG = """\
160
+ MCP clients (Claude Desktop, Claude Code, Cursor, VS Code) start this server themselves.
161
+ Running it by hand starts an MCP server on stdin/stdout that waits for a client.
162
+
163
+ configuration (environment variables or a .env file; unset sources are skipped):
164
+ DATABASE_URL Postgres, e.g. postgresql://stackdoctor_ro:...@localhost:5432/app
165
+ REDIS_URL Redis, e.g. redis://localhost:6379/0
166
+ CELERY_BROKER_URL Celery broker (Redis; RabbitMQ is experimental)
167
+ CELERY_RESULT_BACKEND Redis result backend, for failed tasks
168
+ CELERY_APP optional import path of your Celery app, e.g. myproject.celery:app
169
+ LOG_SOURCES comma-separated log files and/or docker:<container>
170
+ STACKDOCTOR_ENV_FILE path to a .env file (default: .env in the working directory)
171
+
172
+ docs: https://github.com/lepri89/stackdoctor
173
+ """
174
+
175
+
176
+ def main(argv: list[str] | None = None) -> None:
177
+ parser = argparse.ArgumentParser(
178
+ prog="stackdoctor",
179
+ description="Read-only MCP server that diagnoses a Python backend stack "
180
+ "(Postgres, Celery, Redis, logs).",
181
+ epilog=USAGE_EPILOG,
182
+ formatter_class=argparse.RawDescriptionHelpFormatter,
183
+ )
184
+ parser.add_argument("--version", action="version", version=f"stackdoctor {__version__}")
185
+ parser.parse_args(argv) # --help / --version print and exit here
186
+
158
187
  logging.basicConfig(level=logging.WARNING, stream=sys.stderr) # stdout is the MCP channel
159
188
  get_config()
160
189
  mcp.run("stdio")
@@ -0,0 +1,47 @@
1
+ import pytest
2
+
3
+ from stackdoctor import server
4
+
5
+
6
+ @pytest.mark.parametrize("flag,expected", [("--help", "usage: stackdoctor"), ("--version", "stackdoctor ")])
7
+ def test_flags_print_and_exit_without_starting_server(flag, expected, capsys, monkeypatch):
8
+ monkeypatch.setattr(server.mcp, "run", lambda *a, **k: pytest.fail("server must not start"))
9
+ with pytest.raises(SystemExit) as exit_info:
10
+ server.main([flag])
11
+ assert exit_info.value.code == 0
12
+ assert expected in capsys.readouterr().out
13
+
14
+
15
+ def test_unknown_flag_is_an_error(monkeypatch):
16
+ monkeypatch.setattr(server.mcp, "run", lambda *a, **k: pytest.fail("server must not start"))
17
+ with pytest.raises(SystemExit) as exit_info:
18
+ server.main(["--nope"])
19
+ assert exit_info.value.code == 2
20
+
21
+
22
+ def test_version_matches_pyproject(capsys):
23
+ """pyproject.toml is the single source of truth for the version."""
24
+ import tomllib
25
+ from pathlib import Path
26
+
27
+ pyproject = tomllib.loads((Path(__file__).parents[1] / "pyproject.toml").read_text())
28
+ with pytest.raises(SystemExit):
29
+ server.main(["--version"])
30
+ assert capsys.readouterr().out.strip() == f"stackdoctor {pyproject['project']['version']}"
31
+
32
+
33
+ def test_version_fallback_when_not_installed(monkeypatch):
34
+ import importlib
35
+ import importlib.metadata
36
+
37
+ import stackdoctor
38
+
39
+ def missing(name):
40
+ raise importlib.metadata.PackageNotFoundError(name)
41
+
42
+ monkeypatch.setattr(importlib.metadata, "version", missing)
43
+ try:
44
+ assert importlib.reload(stackdoctor).__version__ == "0.0.0+unknown"
45
+ finally:
46
+ monkeypatch.undo()
47
+ importlib.reload(stackdoctor)
@@ -1003,7 +1003,7 @@ wheels = [
1003
1003
 
1004
1004
  [[package]]
1005
1005
  name = "stackdoctor"
1006
- version = "0.1.0"
1006
+ version = "0.1.2"
1007
1007
  source = { editable = "." }
1008
1008
  dependencies = [
1009
1009
  { name = "celery" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes