# AGENTS.md Guidance for coding agents working in the `itasca-mcp` repository. This file is the single source of truth; `CLAUDE.md` only imports it. ## Project Overview `itasca-mcp` provides an MCP server for ITASCA PFC workflows. The bridge runtime that runs inside PFC GUI lives in the [`itasca-mcp-bridge`](https://github.com/yusong652/itasca-mcp-bridge) repo and is consumed here as a git submodule. This repository has two runtime contexts: - `src/itasca_mcp/` (Python >= 3.10): MCP server package used by clients/tooling - `itasca-mcp-bridge/` (submodule, PFC embedded Python often 3.6): HTTP bridge running inside PFC GUI Treat these as separate deployment targets. End users get the bridge from PyPI (`pip install itasca-mcp-bridge`): first install happens via the agentic bootstrap's terminal pip step or `addon.py`, and from then on `itasca_mcp_bridge.start()` self-upgrades on every start. The submodule exists only so contributors can edit bridge code alongside MCP code without two clones. The legacy `pfc-mcp-bridge` PyPI package (last release `bridge-v0.3.3`) is deprecated and no longer maintained — its code lived in `pfc-mcp-bridge/` here and was removed when the submodule replaced it. ## Core Architecture ### MCP side (`src/itasca_mcp`) - Exposes two documentation tools (`itasca_browse_doc` / `itasca_query_doc`, trees selected by `source`) and five execution tools through FastMCP - Communicates with bridge via HTTP client (`itasca_mcp.bridge.client`) - Returns a unified tool envelope: `ok`, `data`, `error` - Dual execution model: synchronous REPL (`itasca_execute_code`) for quick queries, script-first async (`itasca_execute_task` + `itasca_check_task_status`) for long-running simulations ### Bridge side (`itasca-mcp-bridge` submodule) - Runs in PFC GUI process - Owns thread-safe interaction with ITASCA SDK - Handles long-running tasks and diagnostics - Started inside PFC GUI via `itasca_mcp_bridge.start()`, which self-upgrades from PyPI first (best-effort); `addon.py` remains the manual first-install bootstrap - The submodule pin in this repo controls which bridge revision contributors develop against; runtime users always get whatever is on PyPI ## Repository Layout ```text itasca-mcp/ ├── src/itasca_mcp/ │ ├── bridge/ # MCP-side bridge client/task manager │ ├── knowledge/ # command/API/reference search system │ ├── tools/ # MCP tool implementations │ ├── formatting.py # shared response formatting │ └── server.py # MCP server entrypoint ├── itasca-mcp-bridge/ # submodule → github.com/yusong652/itasca-mcp-bridge ├── addon.py # PFC-side first-install bootstrap (pip-installs the bridge) └── tests/ # MCP/tool contract tests ``` **Submodule workflow** (mirrors flac-mcp): - Fresh clone needs `git clone --recurse-submodules ` or, after a non-recursive clone, `git submodule update --init --recursive` - After `git pull` on this repo, re-sync with `git submodule update --recursive` if the pin moved - To bump the bridge pin: `cd itasca-mcp-bridge`, fetch/checkout the new commit, then commit the gitlink update in this repo. Push order: bridge repo first (so the pinned commit exists on its origin), itasca-mcp second - "Modified content" / "untracked content" in `git status` for the submodule is normal during local bridge dev — only commit the gitlink when you actually want to bump the pin ## Development Commands Run from repository root. ```bash uv sync uv sync --group dev uv run itasca-mcp uv run pytest tests/test_phase2_tools.py uv run pytest tests/test_tool_contracts.py ``` ## Engineering Rules 1. Keep MCP and bridge concerns separate. - Do not couple MCP logic to PFC GUI internals. - Do not introduce application/session policy into bridge runtime. 2. Preserve execution semantics for each model. - `itasca_execute_code` runs synchronous snippets and returns stdout/result immediately. - `itasca_execute_task` submits scripts and returns quickly. - Progress/result retrieval goes through `itasca_check_task_status`. 3. Maintain structured tool contracts. - Prefer stable machine-readable keys over ad-hoc text parsing. - Use the unified envelope for all tool business payloads: - success: `{"ok": true, "data": ...}` - error: `{"ok": false, "error": {"code": str, "message": str, "details"?: object}}` - Enforce coherence: `ok=true` must not include `error`; `ok=false` must include `error`. - Do not require duplicate presentation fields (for example, `display`) when they mirror structured data. - Let clients render human-facing formatting from structured fields. - Documentation tools must keep `data` consistent as: - `source`: `"commands" | "python_api" | "fish" | "reference"` (`"all"` for a cross-source query) - `action`: `"browse" | "query"` - `entries`: `list[object]` - `summary`: `object` (counts/hints/context) - Keep query/path/input echo minimal; prefer putting necessary context in `summary` or `error.details.input`. 4. Keep compatibility when practical. - If moving shared helpers, keep thin compatibility re-exports when tests or downstream code rely on old import paths. 5. Respect runtime constraints. - MCP package talks to the bridge over HTTP (`httpx`). - Bridge side is stdlib-only (HTTP + SSE via `http.server`): no third-party runtime dependency, so it installs into any ITASCA embedded Python (3.6+) with no version pins. 6. Judge each dependency by the complexity it carries, not by a blanket "fewer deps is better" rule. The two runtime contexts land on opposite answers for the same reason, not different ones: - Keep a dependency when it owns hard correctness you would not write better yourself. On the MCP side, FastMCP + Pydantic own tool-schema declaration, JSON Schema generation, input validation, and alignment with the MCP protocol — protocol-boundary complexity. Do not reimplement it; do not drop these deps to chase minimalism. - Drop a dependency when it is not carrying real complexity for your semantics. The bridge transport is self-hosted on stdlib HTTP + SSE because the interaction is just request → execute → result, with the server->client push reduced to a payload-free doorbell — a transport library's extra machinery (duplex/heartbeat/version negotiation) is not load-bearing here, and a pinned dependency is itself a liability in the engine's embedded Python. - The rule of thumb: validation at the protocol boundary is outsourced; the execution transport is self-hosted when the semantics are simple enough to not need a library. Minimalism is a consequence of this test, never the goal itself. ## Testing Expectations - For tool/contract changes, run: - `tests/test_phase2_tools.py` - `tests/test_tool_contracts.py` Mock bridge based tests are preferred for deterministic CI. - Add a test only where a regression would be silent and costly: tool envelopes, loader/resolution logic, parser output shape. Do not add one for every change. Corpus data edits (a note on an entry, an availability marker, a removed member marked as removed) and "the removed thing is still removed" checks are not worth a test; the live reconcile/verification run is the evidence, and the commit message records it. ## Documentation Sources Searchable docs live under `src/itasca_mcp/knowledge/resources/{_common,}/`: - `command_docs/` - command syntax per version - `python_sdk_docs/` - Python SDK modules/objects - `fish_docs/` - FISH intrinsic trees, one JSON per namespace (regenerate with `scripts/corpus/generate_fish_docs.py`; kernel namespaces live in `_common`) - `references/` - model properties, range elements, plot items When changing schema/content shape, verify browse/query tool behavior remains consistent. ## Release Process Releases are cut by the maintainer; the step-by-step checklist (version bump, changelog curation, README sweep, `server.json` + MCP registry publish, tagging) lives in [`docs/RELEASING.md`](docs/RELEASING.md). Contributors do not need it: a clear conventional commit is enough (see Commit Style below). CI runs on every push/PR to `main`: ruff check, ruff format, mypy, pytest with coverage. ## Commit Style Use conventional prefixes seen in repository history, for example: - `feat: ...` - `fix: ...` - `refactor: ...` - `test: ...` - `docs: ...` Keep commit messages focused on why the change was needed. Documentation is first-class for this project -- agents rely on the docs and the agentic install guides to understand and operate itasca-mcp, so notable documentation/install-flow changes belong in the changelog alongside behaviour changes. Contributors only need a clear conventional commit message; they do NOT edit `CHANGELOG.md`. The maintainer curates the `## [Unreleased]` section from commit history at release time (see `docs/RELEASING.md` and the convention comment at the top of `CHANGELOG.md`). This keeps the changelog complete without putting changelog friction on any single change or PR.