# adapter-1c-mcp

Thin MCP proxy for the 1C REST adapter.

Target host:

```text
docker.cin.su
```

Runtime URL:

```text
http://docker.cin.su:8021
```

MCP endpoint for Codex:

```text
http://docker.cin.su:8021/mcp
```

The server responds with MCP protocol `2025-06-18` and returns
`Mcp-Session-Id` during `initialize`.

Legacy SSE endpoint:

```text
http://docker.cin.su:8021/sse
```

Direct JSON-RPC endpoint for smoke tests:

```text
http://docker.cin.su:8021/mcp
```

Codex config:

```toml
[mcp_servers.adapter-1c-mcp]
enabled = true
url = "http://docker.cin.su:8021/mcp"
```

Already running Codex Desktop windows can keep the MCP tool list from their
startup time. Restart Codex Desktop, or start a fresh session, after changing
MCP server configuration.

## How The Adapter URL Is Passed

The MCP proxy does not hard-code the 1C adapter address. Pass it with:

```text
ONEC_ADAPTER_URL=http://docker.cin.su:8011
```

Optional adapter bearer token:

```text
ONEC_ADAPTER_TOKEN=
```

Timeout for adapter HTTP calls:

```text
ONEC_ADAPTER_TIMEOUT_SECONDS=4
```

These variables are configured in:

```text
core/deploy/docker/adapter-1c-mcp/.env.example
```

For real deployment, create a non-committed `.env` next to the compose file and set the actual adapter URL/token there.

## REST adapter on docker.cin.su

The REST SQL adapter is deployed from:

```text
core/deploy/docker/adapter-1c/compose.yaml
```

Its published port is set in a non-committed REST environment file:

```text
ADAPTER_1C_HOST_PORT=8011
```

SQL-base administration is available in the REST adapter at:

```text
http://docker.cin.su:<ADAPTER_1C_HOST_PORT>/admin
```

With the default port this is `http://docker.cin.su:8011/admin`. The page is
used to add or update SQL connections for named bases. Do not put connection
passwords in Git or documentation. If the port changes, use the new port both
for this page and in `ONEC_ADAPTER_URL`.

If that port is occupied, select a free port there and set the MCP URL to the
same value, for example `ONEC_ADAPTER_URL=http://docker.cin.su:18011`.
Keep `ONEC_SQL_BASES_JSON` (or the mounted `/data/onec-sql-bases.json`) only
in that external runtime configuration; never place SQL passwords in this
repository.

## Deploy

Deploy both REST adapter and MCP proxy, then run live verification when a test
base is available:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -RestEnvFile <path-to-non-committed-rest-env> `
  -McpEnvFile <path-to-non-committed-mcp-env> `
  -BaseId <base-id-from-project-context>
```

Multiple test bases can be verified in one deploy:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-1>,<base-id-2>
```

Duplicate base ids are rejected before verification starts, so persisted
reports are not overwritten by an accidental repeated base value.

To make post-deploy checks deterministic, pass an explicit metadata object
selector. This is optional; without it the smoke discovers a module-capable
object automatically.

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-from-project-context> `
  -ObjectGuid <metadata-object-guid> `
  -ObjectKind <metadata-kind>
```

Saved-state preparation defaults to `ConfigSave` for the base configuration
save layer. Use `-SavedStateTable ConfigCASSave` when the strict smoke should
target an extension/CAS save layer instead:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-from-project-context> `
  -SavedStateTable ConfigCASSave
```

The deploy script prints Docker container summaries for both services, then the
verify script prints `/health` status and `contract_version` before running the
REST and MCP selector-chain smoke tests. The selector-chain smoke also checks,
when a routine name is available from module metadata, that
`metadata.resolve_overrides` returns `write_plan_evidence.next_resolution` for
`metadata.saved_state.modules.search` with the same `base_id` and routine query,
plus a public object `ref`/`kind`/`name` and semantic
`layer=base_saved_state|extension_saved_state`; public follow-ups never require
`ConfigSave`, `ConfigCASSave`, or `owner_guid`. The smoke then follows that
resolver. If a saved-state stream exposes `write_plan_target`,
the smoke composes it with `write_plan_evidence.target` and checks the resulting
read-only `metadata.write.plan`; when the test base has no saved-state stream,
that last composition step records `skipped_no_saved_state_target`.
These selector-chain reports are written under
`reports/1c-sql/<base-id>/selector-chain-rest-smoke.json` and
`reports/1c-sql/<base-id>/selector-chain-mcp-smoke.json`. Each report includes
a top-level `coverage` block summarizing whether override evidence was found,
whether the saved-state resolver ran, whether a `write_plan_target` was
available, and which steps were skipped. The verify script validates that both
selector-chain report files are written, parse as JSON, pass, and include the
expected `coverage` sections.
Use `-RequireSelectorChainWritePlanComposition` when the test base is expected
to contain a matching saved-state stream and the selector-chain smoke must fail
instead of accepting `skipped_no_saved_state_target`.
Verification also runs the read-only write-plan safety smoke through both REST
and MCP `onec_request`; the MCP smoke also checks that live methods without
`payload.base_id` are stopped by `adapter_1c_mcp_policy.v1` with
`base_id_required`, and that low-level `storage.*`/`query.*` fallback calls are
blocked as `diagnostic_method` unless diagnostics are explicitly requested. The
verify script validates the written write-plan safety reports after each smoke.
Then verification runs REST saved-state form/module write-loop smoke tests
through `metadata.write`; if the test base has no pending
`ConfigSave`/`ConfigCASSave` rows, those saved-state checks record
`skipped_no_saved_state` and pass by default. The saved-state report files are
also parsed and checked after the smoke commands. Before those smoke tests,
verification generates read-only `saved-state-strict-readiness.json` and
`saved-state-copy-plan.json` reports for the selected `-SavedStateTable`. The
copy plan is generated through `scripts/plan_1c_saved_state_copy.py` using the
same object selector arguments, so the later persisted-report check has fresh
copy-preparation evidence. At the end, verification runs
`scripts/check_1c_verify_reports.py` against the persisted report set, so the
same report validation can be repeated offline without calling the adapter.
The offline validator checks that selector-chain coverage includes
`metadata.resolve_overrides` evidence, the expected
`metadata.saved_state.modules.search` next step, saved-state resolution, and
attempted `metadata.write.plan` composition. It also verifies write-plan safety
outcomes: effective form writes must route to `metadata.write.plan`,
uncontrolled replacement must be blocked, controlled replacement must be
planned through `metadata.module.write_apply`, drift must be blocked, and MCP
policy checks must block missing `base_id` and diagnostic fallback.
Saved-state write smoke reports are checked in both modes: empty-state skips
must include successful preflight counts and `skipped=true`; real
write-and-rollback runs must include allowed write plans, expected apply
methods, and rollback evidence. The same persisted report validator also checks
`saved-state-strict-readiness.json` and `saved-state-copy-plan.json` when
saved-state write smoke reports are enabled. Readiness must match the requested
`base_id` and selected save-layer table; in strict mode it must be `ready=true`.
The copy plan must match the requested `base_id`, resolve a concrete object,
list active source rows from the matching storage family, be `plan_ready`, and
show clear target collision status for `ConfigSave`/`ConfigCASSave`. The family
must match the target save layer: `Config -> ConfigSave` for base changes and
`ConfigCAS -> ConfigCASSave` for extension/CAS changes. The validator also
checks that readiness, copy-plan target table, saved-state form smoke table, and
saved-state module smoke `module_ref` table are the same.
`scripts/check_powershell_scripts.py` also guards the verify/deploy wiring for
these strict flags and asserts that `check_1c_verify_reports.py --self-test`
covers selector-chain, safety, saved-state, and copy-plan failure classes.
Persisted report validation also pins the expected JSON `schema` value for each
report type, so stale or unrelated report files fail before their contents are
trusted.
The same validator checks report identity: each persisted JSON must match the
requested `base_id`, and REST/MCP reports must match their expected
`transport`. When verification provides expected endpoints, REST/MCP reports
must also match the configured `endpoint_url`.

To require an actual saved-state write-and-rollback instead of allowing the
empty-state skip:

```powershell
python scripts\check_1c_saved_state_strict_readiness.py `
  --base-id <base-id-from-project-context> `
  --saved-state-table ConfigSave `
  --report reports\1c-sql\<base-id>\saved-state-strict-readiness.json `
  --json
```

This readiness check is read-only. It checks REST adapter health, row counts in
the selected save-layer table, and saved-state form/module discovery. Use
`--saved-state-table ConfigSave` for the base save layer or
`--saved-state-table ConfigCASSave` for the CAS/extension save layer. If it
reports `blocked_no_saved_state_rows`, there are no unactivated Configurator
changes in the selected save layer. To prepare a strict write test, copy the
target object from the main configuration storage (`Config`/`ConfigCAS`) into
the selected save layer through the approved saved-state workflow, then rerun
readiness before enabling strict mode.

To produce a read-only copy preparation plan for a concrete object:

```powershell
python scripts\plan_1c_saved_state_copy.py `
  --base-id <base-id-from-project-context> `
  --kind <metadata-kind> `
  --name <metadata-object-name> `
  --target-table ConfigSave `
  --report reports\1c-sql\<base-id>\saved-state-copy-plan.json `
  --json
```

The copy plan does not write SQL. It lists active storage rows for the selected
object only from the matching source family, checks whether the intended
`ConfigSave`/`ConfigCASSave` target already contains the same `FileName` values,
and records the intended save-layer target. `--target-table ConfigSave` plans
from `Config`; `--target-table ConfigCASSave` plans from `ConfigCAS`. Normal
verification regenerates this file automatically before saved-state smoke tests
and uses `-SavedStateTable` to pick the same target table for the copy plan,
form smoke, and module smoke. Run the command manually when choosing a target
object for strict readiness or when preparing evidence without the full
deploy/verify flow. The offline persisted-report validator checks this file by
default, so regenerate the plan whenever the chosen object, target table, or
base changes.

To generate the reviewed SQL preparation script from that plan without
executing SQL:

```powershell
python scripts\prepare_1c_saved_state_copy_sql.py `
  --plan reports\1c-sql\<base-id>\saved-state-copy-plan.json `
  --expected-base-id <base-id-from-project-context> `
  --expected-target-table ConfigSave `
  --sql-out reports\1c-sql\<base-id>\prepare-saved-state-copy.sql `
  --report reports\1c-sql\<base-id>\prepare-saved-state-copy-sql.json `
  --json
```

This generator does not call the adapter and does not write SQL. It refuses
plans that are not `plan_ready`, have target collisions, or mix storage
families. The generated SQL is a manual review artifact: it starts a
transaction, rechecks that the save-layer target has no planned `FileName`
values, copies only the planned rows with `INSERT ... SELECT`, verifies the
copied row count, and commits only if those guards pass.

To execute the reviewed preparation SQL, use the explicit SQL-write gate. This
is not run by deploy/verify:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\execute_1c_saved_state_copy_sql.ps1 `
  -Server <sql-server> `
  -Database <sql-database> `
  -User <sql-user> `
  -Password <sql-password> `
  -ExpectedBaseId <base-id-from-project-context> `
  -ExpectedTargetTable ConfigSave `
  -IUnderstandThisWritesToSql
```

The executor validates the generated SQL report, checks that the SQL file still
looks like the reviewed saved-state copy artifact, records the SQL SHA1, runs
the SQL through `System.Data.SqlClient`, and immediately calls the read-only
post-copy verifier. Credentials can also be supplied with `ONEC_SQL_SERVER`,
`ONEC_SQL_DATABASE`, `ONEC_SQL_USER`, and `ONEC_SQL_PASSWORD`. Do not commit
credentials or execution reports containing environment-specific connection
details.

After the SQL preparation has been executed, verify the saved-state copy
read-only before enabling strict write smoke:

```powershell
python scripts\verify_1c_saved_state_copy.py `
  --plan reports\1c-sql\<base-id>\saved-state-copy-plan.json `
  --expected-base-id <base-id-from-project-context> `
  --expected-target-table ConfigSave `
  --report reports\1c-sql\<base-id>\saved-state-copy-verify.json `
  --require-ready `
  --json
```

Before the SQL preparation is executed, this verifier is expected to report
`blocked_missing_target_rows`. After preparation it must report `ready=true` by
comparing the save-layer `FileName`, `PartNo`, byte sizes, and `BinarySHA1`
values against the reviewed active-source rows from the copy plan.

Also generate the guarded cleanup script before executing the preparation SQL,
so there is a reviewed way to remove the prepared working-copy rows later:

```powershell
python scripts\prepare_1c_saved_state_cleanup_sql.py `
  --plan reports\1c-sql\<base-id>\saved-state-copy-plan.json `
  --expected-base-id <base-id-from-project-context> `
  --expected-target-table ConfigSave `
  --sql-out reports\1c-sql\<base-id>\cleanup-saved-state-copy.sql `
  --report reports\1c-sql\<base-id>\cleanup-saved-state-copy-sql.json `
  --json
```

The cleanup SQL deletes only the planned `FileName`/`PartNo` rows from the
selected save layer, and only when their current `BinarySHA1` still matches the
reviewed copy plan. It rolls back if the rows are missing, extra/mismatched, or
if the deleted row count differs from the plan.

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-from-project-context> `
  -RequireSavedStateWriteSmoke
```

To require selector-chain evidence to resolve all the way to a concrete
read-only `metadata.write.plan` composition:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-from-project-context> `
  -RequireSelectorChainWritePlanComposition
```

To skip saved-state write-loop checks entirely:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-from-project-context> `
  -SkipSavedStateWriteSmoke
```

To skip only the read-only write-plan safety smoke:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -BaseId <base-id-from-project-context> `
  -SkipWritePlanSafetySmoke
```

To validate the latest persisted reports without contacting REST or MCP:

```powershell
python scripts\check_1c_verify_reports.py --base-id <base-id-from-project-context>
```

Add `--rest-adapter-url <url> --mcp-url <url>` when the persisted REST/MCP
reports should be pinned to specific endpoints.
Add `--saved-state-table ConfigSave` or `--saved-state-table ConfigCASSave`
when the persisted saved-state reports must be pinned to a specific save-layer
table.
Add `--max-report-age-seconds <seconds>` when the persisted reports must also
be fresh enough for a deployment gate.
Use `--skip-saved-state-copy-plan` only when intentionally validating an older
report bundle that does not include `saved-state-copy-plan.json`.

To run the offline validator's synthetic soft/strict self-test:

```powershell
python scripts\check_1c_verify_reports.py --self-test
```

To run the whole offline/static verification-stack preflight:

```powershell
python scripts\check_1c_adapter_verification_stack.py --base-id <base-id-from-project-context>
```

The verification-stack preflight passes the default REST/MCP endpoint URLs to
the persisted report validator. Override them with `--rest-adapter-url` and
`--mcp-url` for non-default deployments.
It also passes `--saved-state-table`, defaulting to `ConfigSave`, so
persisted copy-plan/form/module reports must match the intended save-layer
table.
Pass multiple base ids after `--base-id` to validate several persisted report
directories in one run.
Use `--max-report-age-seconds` to reject stale report files during the
verification-stack preflight.

To save a machine-readable verification-stack summary:

```powershell
python scripts\check_1c_adapter_verification_stack.py `
  --base-id <base-id-from-project-context> `
  --json `
  --report reports\1c-sql\<base-id>\adapter-verification-stack-check.json
```

The saved stack report keeps per-command return codes, compact parsed summaries
for JSON-producing checks, and truncated stdout/stderr tails for diagnostics.
For persisted verify-report checks, the parsed summary also includes per-base
REST/MCP selector-chain composition status, write-plan safety check counts, and
saved-state write smoke status, including the saved-state copy-plan target,
source-row count, and target collision status.

To deploy without live verification:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\deploy_1c_adapter_stack.ps1 `
  -SkipVerify
```

Low-level MCP-only deploy:

```powershell
docker --host ssh://docker.cin.su compose `
  --env-file core\deploy\docker\adapter-1c-mcp\.env.example `
  -f core\deploy\docker\adapter-1c-mcp\compose.yaml `
  up -d --build
```

Container name:

```text
adapter-1c-mcp
```

## Health

```powershell
Invoke-RestMethod http://docker.cin.su:8021/health
docker --host ssh://docker.cin.su ps --filter name=adapter-1c-mcp
docker --host ssh://docker.cin.su logs --tail 80 adapter-1c-mcp
```

Smoke test MCP initialize:

```powershell
$body = @{
  jsonrpc = "2.0"
  id = 1
  method = "initialize"
  params = @{
    protocolVersion = "2025-06-18"
    capabilities = @{}
    clientInfo = @{ name = "smoke"; version = "1" }
  }
} | ConvertTo-Json -Depth 10

Invoke-WebRequest `
  -Uri "http://docker.cin.su:8021/mcp" `
  -Method Post `
  -ContentType "application/json" `
  -Headers @{ Accept = "application/json, text/event-stream" } `
  -Body $body
```

## Tools

The MCP proxy exposes a small stable tool surface:

```text
onec_health
onec_help
onec_request
onec_job_get
onec_job_cancel
access_role_users
access_role_profiles
access_role_audit_export
access_role_audit_analyze
access_user_explain
access_users_search
access_object_explain
access_keys_query
access_object_keys_resolve
access_object_roles
access_object_subjects
access_rls_discover
```

Use `onec_request(method, payload)` for uncommon or newly added adapter methods
until a dedicated alias is useful enough to keep stable.
The `access_*` tools are lightweight aliases for common access-audit workflows;
they only forward to REST adapter methods such as `access.role.users` and keep
all BSP/access-rights logic in the REST adapter.

For object access checks, pass object names/public refs first; the REST adapter
resolves GUIDs, SQL numbers, BSP identifiers, synonyms, and readable role-rights
names internally. Example:

```powershell
python scripts\smoke_1c_access_object.py `
  --base-id upo_test `
  --ref "РегистрСведений.УОП_АктуальныеСпецификации" `
  --action write
```

Use `access_object_roles` or `access_object_subjects` for the question "which
roles/users can read or write this metadata object". Do not depend on
`metadata.objects.list kind=Role`: some live bases do not expose roles as regular
metadata objects through that route.

For BSP data-access keys, keep the metadata object and concrete data record
separate:

```json
{
  "base_id": "upo_test",
  "kind": "object",
  "object_ref": "Справочники.Номенклатура",
  "record_ref": "00112233445566778899AABBCCDDEEFF"
}
```

Here `kind=object` is the `access_keys_query` area, not the metadata kind.
`object_ref` is resolved to the internal BSP SQL number and `record_ref` to the
record id. The same public pair works with `access_object_keys_resolve` and
`access_object_explain`; do not invent `object_sql_number` in MCP code.

For BSP role-right checks, `action=write` means add or modify rights. Read-only
roles must not be returned for `write`; query `action=read` separately when you
need visibility roles. Returned permissions keep `source_fields` from
`ПраваРолей` so suspicious mappings can be verified against the live base.

`access.snapshot.extract` also supports this access graph and defaults to the
BSP extractor preset when `queries` are not supplied. Passing `preset=bsp`
explicitly is still fine, but the old "No extractor queries were provided"
discovery response should not appear for a normal BSP base.

When `access_object_subjects` is called with `include_access_key_scope=true`,
the regular `limit` only trims returned roles/profiles/groups/users. Scope
diagnostics use separate controls: `access_key_scope_subject_limit` limits how
many matched groups/users are checked, and `access_key_scope_limit` limits rows
read from each BSP access-key extractor. Check `access_key_scope.coverage` and
`access_key_scope.truncated` before treating the scope sample as complete.

For local audit artifacts, use the workspace script:

```powershell
python scripts\export_1c_access_role_audit.py `
  --base-id upo_test `
  --role "запись изменение номенклатура поставщиков" `
  --user-threshold 30
```

It writes CSV, JSON export, analysis JSON, and a summary file under
`reports/1c-access/<base_id>/`. By default it also writes a self-contained
HTML report with findings, counters, artifact links, and a filterable user table;
pass `--no-html` to skip it.

Architecture contract:

- The REST adapter owns 1C behavior, payload validation, decoding, owner
  resolution, RAG/tool-facing data shape, and method documentation through
  `help.methods`.
- MCP is a transport proxy: JSON-RPC/MCP framing, adapter URL/token handling,
  generic `onec_request`, job polling/cancellation helpers, and lightweight
  agent-safety checks such as requiring `base_id` for live database methods.
- Do not add method-specific 1C business logic to MCP. Put it in the adapter,
  expose it through `/rpc`, and make it discoverable via `help.methods`.
- Do not register an MCP unified/composite handler with the same name as an
  adapter method; `onec_request(method=...)` must call the adapter method, not
  shadow it inside MCP.
- When adding a new adapter method, verify it through MCP with
  `onec_request({"method": "...", "payload": {...}})`. MCP should not require a
  schema edit for ordinary adapter growth.
- Run `python scripts/check_1c_mcp_adapter_contract.py` before release; it
  verifies that `onec_request` stays generic and that every method listed by the
  REST adapter is forwarded through `/rpc`.

Example:

```json
{
  "method": "metadata.object.get",
  "payload": {
    "base_id": "<base_id-from-project-context>",
    "kind": "document",
    "name": "ПриходнаяНакладная",
    "view": "merged"
  }
}
```

Do not copy placeholder or sample `base_id` values from documentation into real
requests. For live database methods, get `base_id` from the current project,
user request, environment, or another authoritative context.

## Agent Request Policy

The MCP proxy blocks live database methods without `payload.base_id` before
calling the REST adapter. This is intentional: agents must not probe metadata,
modules, extensions, queries, storage, or code without a concrete target base.

Affected method families include:

```text
metadata.*
modules.*
code.*
templates.*
diagnostics.*
extensions.*
query.*
storage.*
schema.*
codec.*
```

Allowed without `base_id`:

```text
onec_help / help.methods
onec_health without a base for generic service health
adapter.job.get / adapter.job.cancel
```

Agent rules:

- If `base_id` is unknown, ask for it or obtain it from project context.
- For object-scoped calls, use one of the public selector shapes supported by
  the adapter: `ref`, `kind` + `name`, `guid`, or MCP-friendly
  `object_type`/`object_name`/`object_guid`. Do not add MCP-side conditions for
  concrete object names.
- If a prior result contains `module_ref`, `module_id`, GUID, or read selector,
  use direct read methods before global search.
- If a search result contains `read_selector.method`, call that method with the
  selector payload. `code.search` selectors point to `code.read`; `modules.search`
  selectors point to `modules.read`.
- If a result contains `related_selectors`, prefer those payloads for the next
  object-scoped call. They preserve `kind`/`name`/`guid` and include `ref` when
  the adapter knows the canonical object kind and name.
- When `modules.search` or `code.search` resolves a module owner, keep both the
  public owner selector (`ref`, `kind`, `name`, `guid`) and the opaque
  `module_ref` in the next read payload. `module_ref` is the direct read handle;
  `ref` makes the owner clear to the agent and later calls.
- When narrowing `code.search` to one module, pass `module_ordinal` together
  with the object selector. The adapter response should remain a public
  `onec_code_search.v1` object with `item.read_selector`, not a low-level tuple
  or storage result.
- For BSL edits, prefer high-level `metadata.write` with a 1C canonical path,
  `routine_text`, and `routine_operation`. Do not ask the user for SQL/save
  gates: the adapter prepares saved-state when needed, writes only to the
  saved-state working layer, and does not activate changes. Use
  `metadata.form.command_button.write` for the complete form command + visible
  button + handler workflow. `code.write` remains a compatibility shortcut for
  simple module edits.
- For a metadata object's standard `synonym` or `comment`, call generic
  `onec_request` with `method=metadata.object.property.write`, a public
  `ref`/`kind` + `name`, and `allow_saved_state_write=true`. Prefer
  `execution_mode=plan` first. The adapter resolves GUIDs and serialized paths;
  MCP callers must not pass SQL tables or file names. Object rename and adding
  a new synonym locale are intentionally unsupported.
- For an existing requisite, tabular section, dimension, or resource, add a
  public `member_ref` such as
  `Catalog.Номенклатура.Attribute.Артикул`. When only
  `member_kind` + `member_name` is used and several identities match, preserve
  the adapter's `ambiguous` result and retry with the full member path.
- To add a requisite, call `metadata.object.member.add` through `onec_request`
  with a full `template_member_ref` from the same object and the new public
  name/synonym. Never invent a GUID, Config table, collection path, or type
  descriptor in MCP code; the new requisite inherits type/settings from the
  named template.
- The same method adds a tabular-section column when
  `template_member_ref` contains the full
  `...TabularSection.<name>.Attribute.<column>` path. Keep the complete path so
  duplicate checks and generated identity remain scoped to that table part.
- For unresolved module owners, inspect `diagnostics.owner_resolution` and
  `counts.owner_scan_limit_hit`; narrow by `kind`/`name`/`guid` or increase
  `owner_scan_limit` before falling back to broader searches.
- Do not treat `not_found` from `metadata.definition.find` or scoped search as
  proof that code is absent in extensions or ConfigCAS.
- Treat `partial`, `truncated=true`, scan limits, and timeouts as incomplete
  evidence.

Offline selector-chain smoke:

```powershell
python scripts\smoke_1c_mcp_selector_chain.py --json --no-report
```

Saved-state BSL write smoke:

```powershell
python scripts\smoke_1c_code_write_saved_state.py `
  --adapter-url http://docker.cin.su:8011 `
  --base-id <base-id-from-project-context> `
  --extension <extension-name> `
  --object-type CommonForm `
  --object-name <form-name> `
  --routine-name <routine-name> `
  --json
```

This smoke performs idempotent `code.write mode=apply` checks with the current
routine text, a unique `old`/`new` fragment, and the current full
`module_text`. It then verifies `code.read state=working`,
`code.read state=both`, marker-hiding for saved form modules, and saved-state
form indexing. Every write step must report `write_mode.target=saved_state`
and `activation_state=not_activated`.

Agent working-view report:

```powershell
python scripts\report_1c_agent_working_view.py `
  --adapter-url http://docker.cin.su:8011 `
  --base-id <base-id-from-project-context> `
  --extension <extension-name> `
  --object-type CommonForm `
  --object-name <form-name> `
  --routine-name <routine-name> `
  --json
```

This read-only report shows the form names that an agent sees in the
working/save layer and verifies that `code.read state=working` and
`state=both` prefer `saved_state` when saved code exists.

The concise agent-facing coding rules are kept in
`docs/runbooks/1c-agent-coding-contract.md`.

Run this with the contract checks before release. It validates generic agent
routes through `onec_request` and rejects concrete object names in selector
examples.

Optional live selector-chain smoke against a real adapter/base:

```powershell
python scripts\smoke_1c_mcp_selector_chain.py `
  --live `
  --transport rest `
  --adapter-url http://docker.cin.su:8011 `
  --base-id <base-id-from-project-context> `
  --json `
  --no-report
```

To verify both deployed layers with one command:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\verify_1c_adapter_deployment.ps1 `
  -BaseId <base-id-from-project-context>
```

By default this also writes
`reports/1c-sql/<base>/code-write-saved-state-rest-smoke.json` and
`reports/1c-sql/<base>/code-write-saved-state-mcp-smoke.json` when the default
saved-state code-write target exists. The REST verification also writes
`reports/1c-sql/<base>/agent-working-view.json` to pin the save-first working
view that agents should use for coding. Use
`-RequireCodeWriteSavedStateSmoke` to make that smoke mandatory, or
`-SkipCodeWriteSavedStateSmoke` to skip it for bases where the scenario is not
available.

Pass multiple base ids to run the same REST and MCP checks against each base:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass `
  -File scripts\verify_1c_adapter_deployment.ps1 `
  -BaseId <base-id-1>,<base-id-2>
```

Use `-ObjectRef`, or `-ObjectKind` with `-ObjectName`/`-ObjectGuid`, to verify a
specific module-capable metadata object instead of auto-discovery.
Use `-SavedStateTable ConfigSave` or `-SavedStateTable ConfigCASSave` to choose
which save-layer table is used by the copy plan and saved-state write smokes.
Add `-RequireSelectorChainWritePlanComposition` when the selected base/object
must have a saved-state stream that lets the selector-chain smoke compose a
concrete read-only `metadata.write.plan`. The same strict mode also requires
the write-preflight smoke to discover both an extension form and an extension
module by public names (`extension/ref/form/member` and
`extension/ref/form/module/stream_ordinal`), compose allowed plans, and prove
that the repository and support gates use the same resolved
`extension:<GUID>` layer. Module search restores the public extension name from
`ConfigCASSave`; it never returns the storage GUID as the caller-facing
selector. The smoke then submits each public selector with a deliberately
different GUID and requires a read-only
`blocked / extension_selector_conflict` result, so a legacy permissive
repository profile cannot authorize a mismatched layer. Run both checks
directly with
`scripts/smoke_1c_write_preflight.py --require-name-first-extension-form --require-name-first-extension-module`.

To exercise the MCP proxy itself, switch transport and URL:

```powershell
python scripts\smoke_1c_mcp_selector_chain.py `
  --live `
  --transport mcp `
  --mcp-url http://docker.cin.su:8021 `
  --base-id <base-id-from-project-context> `
  --json `
  --no-report
```

Live mode discovers a module-capable object from metadata lists and then checks
`adapter.help.methods -> metadata.definition.find -> related_selectors.modules
-> metadata.object.modules -> modules.read -> code.search ->
item.read_selector -> code.read` using the returned selector and a token derived
from module text or routine metadata. The `adapter.help.methods` step verifies
live REST `contract_version` and `selector_capabilities`, so stale REST adapter
images fail before the longer chain. MCP transport also performs `initialize`,
checks `tools/list` for the same `contract_version` and the generic
`onec_request` selector schema, keeps `Mcp-Session-Id`, and sends adapter calls
through `tools/call` + `onec_request`. This catches stale MCP proxy images where
REST works but agents still see old tool descriptions. Do not commit real base
ids or captured reports from live runs.
- Avoid increasing global search limits when a direct selector is already
  available.
- Do not request `include_storage=true` merely to read a module found by search;
  use the public `read_selector` first.

The request is proxied to the REST adapter through `POST /rpc`:

```json
{
  "method": "metadata.object.get",
  "payload": {}
}
```

The MCP proxy intentionally does not know adapter-specific 1C methods. Add new
methods in the REST adapter and expose them through `help.methods`; MCP stays
unchanged unless the MCP transport itself changes.
