Development¶
Setup¶
git clone https://github.com/kiliantscherny/probo-mcp && cd probo-mcp
uv sync # installs the package plus the dev group (pytest, ruff, ty, tox, zensical)
uvx pre-commit install # optional: ruff on every commit
Everyday commands¶
| Task | Command |
|---|---|
| Tests (with coverage) | uv run pytest --cov |
| Lint | uv run ruff check . |
| Format | uv run ruff format . |
| Type check | uv run ty check |
| Docs preview | uv run zensical serve |
| Everything, like CI | uv run tox |
| One tox env | uv run tox -e 3.13 · lint · type · docs · fix |
tox uses tox-uv with the lock file (uv-venv-lock-runner). The
environments install the matching dependency groups: test, lint, type or docs. It runs the tests
on Python 3.12, 3.13 and 3.14, and coverage must stay at 90% or above.
Tests¶
| File | What it covers |
|---|---|
test_parsers.py |
HTML → models, against the fixtures in tests/fixtures/ |
test_dates.py |
ProBo's mixed date formats (14 Oct 2026 - 18:22, 15 Okt 2026, 13. oktober 2026 12:01, …) |
test_client.py |
Login, CSRF, session renewal, concurrent logins, caching, the allowlist |
test_config.py |
Settings and op:// resolution (with a fake op binary) |
test_service.py |
Archive walking, date filtering, text extraction, safe file names |
test_server.py |
End-to-end through a real mcp.Client, on both protocol eras |
test_main.py |
The command line |
The fixtures are synthetic. They copy ProBo's markup (tags and class names) but contain made-up
content. conftest.py serves them from a fake site with respx,
including the login flow and a generated PDF, so no test touches the network.
Testing against a real site¶
cp .env.example .env # your site and op:// references
op run --env-file .env -- npx @modelcontextprotocol/inspector uv run probo-mcp
Testing the UI¶
Run the server over HTTP and open it in the MCP Apps reference host
(basic-host):
op run --env-file .env -- uv run probo-mcp --transport streamable-http --port 3001
# in a checkout of modelcontextprotocol/ext-apps:
cd examples/basic-host && npm install && npm run build && npx tsx serve.ts
# open http://localhost:8080 and call open_probo_browser
The host runs in the browser, so the MCP endpoint must allow its origin (CORS). Wrap
mcp.streamable_http_app() in Starlette's CORSMiddleware for local testing.
When ProBo changes its HTML¶
- Find the page in your browser and inspect the elements a parser uses.
- Update the fixture in
tests/fixtures/to match the new markup. Keep the content synthetic: never commit real names, addresses or messages. - Fix the selector in
parsers.pyuntiluv run pytestpasses.
Claude Desktop bundle¶
The script generates manifest.json from the live server, so the tool list can't drift. It then
stages the package with a uv runtime shim, and validates and packs with
@anthropic-ai/mcpb.
Releasing¶
- Bump
versioninpyproject.toml(uv version --bump minor) and update the changelog. - Commit, then tag and push:
git tag v0.2.0 && git push --tags. - The Release workflow checks that the tag matches the version, runs the checks, builds the wheel,
sdist and
.mcpb, and publishes a GitHub release with them attached.
Project layout¶
src/probo_mcp/
├── __main__.py CLI (stdio / streamable HTTP)
├── server.py MCPServer assembly: metadata, lifespan, cache hints, extensions
├── tools.py tools (+ elicitation resolver)
├── resources.py probo:// templates + completions
├── prompts.py prompts
├── ui/ MCP App: browser.html + vendored ext-apps SDK
├── state.py lifespan state + error translation
├── service.py ProBo operations (no MCP)
├── parsers.py HTML → models (no I/O)
├── markdown.py models → Markdown
├── client.py HTTP: login, allowlist, cache
├── config.py settings + op:// resolution
└── models.py Pydantic output models