6.4 KiB
AGENTS.md — Workflow for AI agents on finn-eiendom-mcp
This is the master doc for any AI agent (Claude, Copilot, Cursor, etc.) working in this repo. Read this first, then the more specific files it references.
Read order
Before changing code, read:
PRD.md— what we're building and why. Especially §17 ("Code ownership and anti-duplication") — that section is the constitution.PROJECT.md— module map.- This file — workflow.
- The relevant
.github/instructions/*.md:python.instructions.md— Python conventions.mcp.instructions.md— MCP tool rules.cli.instructions.md— CLI command rules.tests.instructions.md— testing conventions.clean-code.instructions.md— best practices and DRY enforcement.docs.instructions.md— when and how to use the context7 MCP server for library documentation.
If something in code contradicts the PRD, the PRD wins. If you change behavior, update both the PRD and the relevant instruction file in the same change.
Runtime — local venv (default)
This project runs in a project-local virtualenv. Docker is supported for packaging but is not required for development.
One-time setup
# from the project root
uv venv # or: python3.12 -m venv .venv
source .venv/bin/activate
uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
Python 3.12+ is required.
Daily commands
All commands are run inside the activated .venv:
pytest # tests
ruff check . # lint
ruff format . # format
mypy finn_eiendom # type-check
finn-eiendom --help # CLI entrypoint
finn-eiendom-mcp # MCP server (stdio)
finn-eiendom serve --transport http --port 8010 # MCP server (HTTP)
Never
- Install packages globally (
pip install ...outside a venv). - Use
sudo pip. - Mutate the host Python.
- Add dependencies without updating
pyproject.toml.
Adding a dependency
uv pip install <package> # ad-hoc, then:
# edit pyproject.toml to record it
uv pip install -e ".[dev]" # reinstall in editable mode
Architecture in one screen
cli.py (typer) mcp_server.py (FastMCP) ← thin, parallel front ends
\ /
\ /
service.py ← orchestration: get_or_fetch, analyze_*
↓
analysis.py ← shortlist + summary
↓
search / ad / eiendom_no / scoring / feedback
↓
parser / http / cache
↓
FINN HTML + Eiendom.no JSON + SQLite
formatting.py sits next to service.py and is shared by both CLI and MCP for json, markdown, and table rendering.
The single-home rule: every piece of logic has exactly one home. If you're tempted to add it in two places, you're wrong about one — push it down a layer and call it from both. See PRD.md §17.2 for the full ownership table.
The five hard rules
These are non-negotiable. Architecture tests in tests/test_architecture.py enforce them.
mcp_server.pyandcli.pyare siblings. They never call each other. Both call onlyservice,formatting,models, andconfig.service.pyis the only place that combines cache + fetch. Nothing above it touches HTTP or SQLite directly.httpxlives inhttp.py. Nowhere else.sqlite3lives incache.py. Nowhere else.- Output formatting lives in
formatting.py. No inline rendering in CLI or MCP tool bodies.
If you have to break one of these to ship a feature, the feature is wrong — fix the design first.
Adding a feature — the checklist
For any new tool / command / behavior:
- Decide the home using the table in
PRD.md§17.2. - Write the function in
service.py(or extendanalysis.pyif it's pure orchestration). - Add a test in
tests/test_service.py. - Add a thin MCP tool in
mcp_server.py—response_formataware. - Add a thin CLI command in
cli.py—--formataware. - Add the renderer in
formatting.pyif output is non-trivial. - Add tests in
tests/test_mcp_server.pyandtests/test_cli.py. - Update
PRD.mdand any affected.github/instructions/*.md.
If steps 4 or 5 need more than ~20 lines, logic has leaked out of the service layer. Push it back down.
Clean code
See .github/instructions/clean-code.instructions.md. Highlights:
- Type hints everywhere.
- Functions stay small; one job per function.
- Names describe intent (
get_or_fetch_ad, notprocess). - Comments explain why, never what the code already says.
- DRY: if you write the same regex / SQL / format string twice, extract it.
- Errors fail loudly with actionable messages. No silent
except: pass. - No dead code, no commented-out blocks left in the tree.
Documentation lookups — use context7
When uncertain about a library's API (FastMCP decorators, Pydantic v2 validators, Typer command patterns, httpx async, msgpack, pytest-asyncio, respx, BeautifulSoup selectors, etc.), use the context7 MCP server. Do not guess from training-data memory.
Pattern (full details in .github/instructions/docs.instructions.md):
context7:resolve-library-idwith the library name → get the canonical ID.context7:query-docswith that ID + a focused topic.
Use context7 before writing the code, not after a test fails. If context7 returns nothing useful, search the library's official docs, then write the smallest possible spike to verify.
Safety and compliance
- Private, low-frequency use only.
- Respect FINN / Eiendom.no rate limits and bot protection.
- Cache aggressively; never bulk-harvest.
- stdio MCP servers log to stderr only — anything on stdout breaks the JSON-RPC frame.
- Scores and estimates are decision support, never legal / technical / financial advice.
Implementation order (Phase 2)
Follow PRD.md §29 step-by-step. Each step is independently mergeable:
- Switch dev workflow to local venv + update instruction files (this change).
- Pydantic v2 cleanup.
- Service layer + tests.
- Formatting layer + tests.
- HTTP retry on 5xx + tests.
- Replace FastAPI with FastMCP stdio server.
- CLI with typer.
- Diff workflow.
- Compare workflow.
- Similar-to-liked.
- Architecture tests.
- README + Claude Desktop config.