CLAUDE.md · git:20260915.36806eb · 2026-09-15 · sha256 4b373b5070a3ffe8
CLAUDE.md git:20260915.36806ebA
Immutable. This exact content is served forever at /api/v1/blob/4b373b5070a3ffe8.
# CLAUDE.md Guidance for Claude Code (claude.ai/code) and other AI agents working in this repository. Machine-specific paths, ports and workspace names belong in a local `CLAUDE.local.md`, which is gitignored - keep them out of this file. ## Project Overview AI-EDT is an Eclipse RCP plugin for 1C:EDT that implements the Model Context Protocol (MCP), so AI assistants can work against the live EDT model instead of scraping project files. Java 17, OSGi. - **Plugin ID:** `ru.aiedt.mcp.server` - **Version:** poms carry the next release as a snapshot - one micro above the newest tag, so a tree at `0.2.36-SNAPSHOT` sits on top of `v0.2.35`. The shipped version comes from the release tag (tycho-versions rewrites every pom during the release run), so what the tree carries never reaches a release; it exists so a local build outranks the release it will replace. Bump it once, right after a release. Between releases only the timestamp qualifier moves. - **Target platform:** EDT 2026.1 (pulled from `edt.1c.ru` by `mcp/targets/default/default.target`) - **License:** AGPL-3.0-or-later. See `LICENSE`, the per-file headers, and `docs/PROVENANCE.md`. - **Origin:** the project began as a fork of `DitriXNew/EDT-MCP` and has since been fully de-derived; `docs/PROVENANCE.md` records the history and the retained notices. ## Critical Rules - **ALWAYS BUILD WHEN CODE CHANGES ARE COMPLETE.** After finishing a set of Java changes, run the Maven/Tycho build yourself - do not ask for approval and do not defer it. - **ALL CODE AND INTERFACE MUST BE IN ENGLISH.** - Before committing, verify `git diff --cached` carries no private data: real project or customer names, absolute local paths, IP addresses, credentials, non-default dev ports. - **A CHANGE IS NOT DELIVERED UNTIL IT IS DESCRIBED AND RELEASED.** When work lands on `main`, five things follow before the task is done: the tree version is bumped one micro above the newest tag; `README.md` and `README.en.md` say what the user can now do, both or neither; `CHANGELOG.md` gains a row for the release, and `docs/tools/README.ru.md` gains the arguments a caller has to know; the skill under `skills/ai-edt/` learns the new tool names, operations and arguments, and so do the copies of it that live outside this repository, which `CLAUDE.local.md` lists; and the announcement is written. Code that only a reader of the diff knows about has not been shipped, and a skill that still describes the old behaviour teaches the agent the new one does not exist. - **PARITY BY FUNCTION, NOT BY NAME.** When adding a capability another plugin also has, implement it under our own name at every visible layer - the MCP tool name, the operation name inside a facade, and the Java class name. Another product's name is admissible only as a hidden compatibility alias, and only when a concrete client depends on it. A collision in a new public identifier (tool, operation, response tag, preference key) is a defect to rename, not to ship. Rationale: every name-level parity pass regrows structural similarity with the upstream this project separated from. Design new mechanisms from our own architecture. ## Build System Maven + Tycho 4.0.5. Modules under `mcp/`: ``` mcp/ bom/ Bill of Materials, parent POM with build plugin versions bundles/ main plugin bundle (ru.aiedt.mcp.server) tests/ JUnit fragment of the bundle features/ Eclipse feature for installation targets/ target platform definition repositories/ p2 update site output ``` `build.cmd [EDT_INSTALL_DIR]` autodetects EDT, injects the Directory location into the target, runs Maven and restores the target. It needs Maven 3.9+ and JDK 17 on PATH. ### Agent build procedure `build.cmd` is unreliable when driven from an agent shell. Do the three steps manually: 1. **Inject** a Directory location before `</locations>` in `mcp/targets/default/default.target`, pointing at a local 1C:EDT component directory. That directory is only a donor for `felix.scr` during the build; the target platform itself still resolves EDT from `edt.1c.ru`. 2. **Build**: `mvn clean verify` in `mcp/`, with JDK 17 as `JAVA_HOME`. Takes several minutes and runs the whole test suite in a real headless OSGi runtime. Never pass `-DskipTests`: it hides every test. Always confirm `BUILD SUCCESS` in the output. 3. **Restore** the target: `git checkout mcp/targets/default/default.target`. Output: `mcp/repositories/ru.aiedt.mcp.server.repository/target/repository/` plus a `.zip` archive. ### Testing `mcp/tests/ru.aiedt.mcp.server.tests` is a JUnit 4 fragment of the plugin bundle, executed by tycho-surefire inside a headless OSGi runtime (`useUIHarness=false`). `mvn verify` runs it and a red suite fails the build. The runtime needs the SWT binary fragment named explicitly (the `swt.fragment` property plus os-activated profiles in the tests pom). Without it `org.eclipse.ui.workbench` dies while activating with `NoClassDefFoundError: org/eclipse/swt/SWTError`, which takes the whole launch down with `error code 13`, because this bundle's Activator extends `AbstractUIPlugin`. On Linux the same runtime also needs an X display, which is why CI runs Maven under `xvfb-run`. When a launch fails, the explanation is in `mcp/tests/ru.aiedt.mcp.server.tests/target/work/configuration/*.log` - Maven itself only prints the exit code. Test counts are most reliably read from `mcp/tests/**/surefire-reports/*.xml`. Supporting scripts: - `scripts/run-unit-tests.sh` - fast local runner on a plain classpath, no OSGi. Convenient while iterating, but it cannot resolve platform classes and reports those tests as `missing-runtime`. `mvn verify` is the authority. - `scripts/test-coverage-audit.py` - census of every production class against the suite (`direct` / `exercised` / `tool-sweep` / `ui-bound` / `workspace-bound` / `untested`), report in `docs/test-coverage.md`. `--check` fails while anything sits in `untested`, so a new class without a test breaks the census instead of sliding in unnoticed. CI runs it. - `scripts/check-declared-arguments-are-read.py` - census of every argument a tool declares against the code that reads it. A client builds its call from the schema, so an argument advertised and read by nothing is a promise the server does not keep: the call succeeds, the value is dropped, and the caller reads success as applied. A name read by a shared helper under `support/` counts as read, and a facade declaring what its delegate reads is listed in the script with the reason. CI runs it. - `scripts/check-identifier-word-class.py` - census of every regular expression whose text carries Cyrillic. Java reads `\w`, `\b` and `CASE_INSENSITIVE` as ASCII unless told otherwise, while 1C source is written in Cyrillic and does not distinguish case, so a pattern built on the defaults answers nothing about Russian code and that reads as a clean result. Each such pattern must declare `UNICODE_CHARACTER_CLASS` where it reads a word class or boundary and `UNICODE_CASE` where it folds case; patterns that read ASCII by nature are listed in the script with the reason. CI runs it. - `scripts/check-protocol-conformance.py` - runs the official MCP conformance suite (`@modelcontextprotocol/conformance`, fetched with npx) against a **running** server, so the question "does this obey the wire specification" is answered by a client written by the people who write the specification rather than by reading our own source. Covers the handshake, version and capability negotiation, the session header, `Accept`/`Content-Type`, `isError`, `ping`, the SSE streams and DNS-rebinding protection. `scripts/conformance-baseline.yml` pins the scenarios that fail on purpose - the ones wanting test fixtures a production catalogue must not ship, and the capabilities this server does not declare. A scenario failing outside that list is a protocol defect; one inside it that starts passing means the entry comes out. Baseline captured 2026-09-12: 9 passed, 23 failed. It refuses when nothing is listening rather than passing, because a gate that goes green having checked nothing is worse than no gate. ## Architecture ### Core server - **Activator** (`Activator.java`) - OSGi entry point. Opens the EDT service trackers and creates the server singleton. Two CLI-API services are tracked by service-name string and typed `Object` on purpose: a typed tracker would add a compile dependency on `com._1c.g5.v8.dt.cli.api` and the plugin would fail to resolve on EDT installs without it. `Activator.logDebug` routes to an opt-in, size-capped trace file in the plugin state location, gated by a preference that defaults to off. - **McpHttpEndpoint** (`McpHttpEndpoint.java`) - embedded HTTP server (`com.sun.net.httpserver`). Endpoints: `POST /mcp` (JSON-RPC), `GET /mcp` (info), `GET /health`. Its `registerTools()` builds the tool list at startup. Tracks the running tool call and supports operator interrupt signals. - **McpAutoStart** (`McpAutoStart.java`) - `IStartup` hook that starts the server with EDT. - **McpRequestRouter** (`wire/McpRequestRouter.java`) - MCP JSON-RPC 2.0 processing: `initialize`, `tools/list`, `tools/call`, with Streamable HTTP and SSE. ### Tool system - **IMcpTool** (`toolkit/IMcpTool.java`) - every tool implements `getName()`, `getDescription()`, `getInputSchema()`, `execute(params)`, `getResponseType()` (TEXT, JSON or MARKDOWN). - **McpToolCatalog** (`toolkit/McpToolCatalog.java`) - thread-safe registry singleton. Registration and enablement are separate questions: a tool is registered at startup and may still be switched off by preferences. Each tool owns a capability id; a duplicate capability is a programming error. - **Tool implementations** live in `toolkit/ops/`. There are 129 callable tool names (the count `scripts/check-tool-catalog.py` reconciles between `ToolCategory` and the catalog). Under the default Canonical preset `tools/list` advertises 46 of them: the facades plus the tools no facade covers. The rest stay hidden but callable as backward-compatible aliases. These three numbers go stale on every tool added - read them off the script and a live `tools/list` rather than trusting what is written here. - **Facades** - `code_search`, `launch_debugger`, `edit_metadata`, `diagnostics`, `project_admin`, `infobase_admin`, `config_io`, `insights`, `security_audit`, `docs_lookup`, `workspace_marks`, `extension_workshop`, `yaxunit_tests` and others expose many operations behind one tool name. - **Naming convention**: MCP tool names and operation names are `snake_case`; JSON parameter keys stay `camelCase`; Java methods stay `camelCase`. ### Features - **Markers** (`labels/`) - persistent metadata tagging stored in `.settings/aiedt-markers.yaml`. Service, dialogs, keyboard shortcuts, Navigator decorator, refactoring sync. - **Clusters** (`folders/`) - custom folder hierarchy in the Navigator per metadata collection, stored in `.settings/aiedt-clusters.yaml`. Service interface with an internal implementation, content and label providers, Navigator filter. - **Operator signals** (`OperatorSignal.java`, `workbench/`) - status bar contribution showing live tool execution, letting the user interrupt a call (cancel, retry, background, expert, custom). ### Preferences Eclipse preference store; keys in `settings/PrefKeys.java` (default port 12250, auto-start, checks folder, plain-text mode for client compatibility, default and maximum result limits). Tool presets live in `settings/ToolProfile.java`, the preference pages in `settings/`. ### Bundled libraries In `mcp/bundles/ru.aiedt.mcp.server/lib/`: `snakeyaml-2.2.jar` (YAML storage), `jsoup-1.17.2.jar` (HTML processing), `copy-down-1.1.jar` (HTML to Markdown). ## Source Layout All Java source sits under `mcp/bundles/ru.aiedt.mcp.server/src/ru/aiedt/mcp/server/`: | Package | Purpose | |---------|---------| | `(root)` | Activator, McpHttpEndpoint, McpAutoStart, McpHistory, OperatorSignal, RunningToolCall | | `toolkit/` | IMcpTool, McpToolCatalog | | `toolkit/ops/` | tool implementations, including every facade | | `toolkit/mdreport/` | metadata report formatting | | `wire/` | MCP request routing, schema composition, JSON and result plumbing | | `wire/jsonrpc/` | JSON-RPC request, response and handshake DTOs | | `settings/` | preference keys, tool presets, preference pages | | `support/` | shared helpers: project state, lifecycle, BSL modules, markdown, reflection, EDT model access | | `labels/` | marker service, storage, model, refactoring hooks | | `labels/ui/`, `labels/handlers/` | marker dialogs, decorator, commands | | `folders/` | cluster service, repository, model, refactoring hooks | | `folders/ui/`, `folders/handlers/` | cluster dialogs and commands | | `navigation/` | Navigator tree commands | | `workbench/` | status bar contribution and operator dialogs | | `session/` | session change tracking | ## Adding a New MCP Tool 1. Create a class implementing `IMcpTool` in `toolkit/ops/`. 2. Implement `getName()`, `getDescription()`, `getInputSchema()`, `execute()`. 3. Override `getResponseType()` if the default MARKDOWN is wrong for it. 4. Register it in `McpHttpEndpoint.registerTools()`. 5. Add a test. `scripts/test-coverage-audit.py --check` fails on an untested production class. 6. Check the new public name against the rule in Critical Rules before merging. ## Check Descriptions `mcp/bundles/ru.aiedt.mcp.server/checks/` holds markdown descriptions of EDT validation checks (for example `bsl-variable-name-invalid.md`), packaged into the plugin jar through `build.properties`. The `get_check_description` tool reads them as a bundle resource unless an external checks folder is configured in preferences. ## Client Connection Default server URL: `http://localhost:12250/mcp`. Client configuration examples are in `docs/clients.md`; `.vscode/mcp.json` and `.mcp.json.example` are ready to copy.