Skip to main content
Glama

MCP server for 1C configuration structures

tests license python

A reference for the metadata of several 1C configurations, for the platform syntax and for the query language — for agents writing BSL code. It returns a minimally sufficient slice: resolving a human wording into the exact object name, searching for ready-made procedures in a code dump, their signatures, limited windows of bodies and back-references from calls, metadata and forms, the object structure at the required level of detail, its links, descriptions of platform methods taking into account the version of the specific configuration and query language constructs.

It does not replace grep over the project's working sources: the server searches for procedures only in the loaded configuration dump into files and does not see unloaded edits. The boundary of sources is fixed in docs/data-sources.md.

Status — as of 2026-08-21

Stage

Status

Dump processing for 1C

✅ 20 metadata kinds, 8.3.5 and 8.3.23, XML and JSON

Dump format

schema v1

Loader, model, link graph, render

✅ 5 configurations, 20,522 objects, 322 thousand edges

Platform help

✅ merged index of three versions, 25,691 items, since/until boundaries

Query language

shquery_ru.hbk, 127 pages, separate source without versions

Search

✅ 97.1% help, 94.7% query language, 90.5% metadata — see "Measured"

Register virtual tables

✅ ready-made query field names (КоличествоОстаток)

Replacement table for old platforms

✅ unavailable is not just forbidden, but replaced with a recipe

Source registry, version mapping

MCP server, 10 tools

✅ streamable-http and stdio

Docker

✅ single container, 357MB per Docker CLI output

Search index cache

✅ 12 MB, loads instead of re-parsing

Benchmark stand

python -m mcp1c.bench, P@k, MRR, gap, mark verification

Tests

.venv/bin/python -m pytest, 1100

Dashboard

✅ registry, sources, query runs, link graph, cards, dictionary

Authorization

API_TOKEN for read, ADMIN_TOKEN for write

Accepting a configuration dump into files

data/incoming/, source filtering and accounting

Code indexes from a dump into files

✅ search, procedure card and reverse call search

The final image size was checked on 2026-08-21: the command docker image ls mcp1c:latest --format '{{.Size}}' output 357MB. This is the size displayed by the Docker CLI. The command docker image inspect mcp1c:latest --format '{{.Size}}' returned 77950901 bytes of the internal field; this is a different metric, not a second measurement of the displayed size.

There are no restrictions on the configuration. Any one loads — standard (accounting, payroll, document flow, retail) and industry-specific: the dump processing walks the metadata rather than knowing it by heart. Whatever was dumped gets parsed.

What matters is the platform version on which the configuration runs: it determines what get_syntax will show as available and what it will hide as later. Verified on 8.3.5 and 8.3.23 — these are the boundaries of what has been encountered; the help is merged from three versions, 8.3.5, 8.3.23 and 8.3.27. The dump processing, meanwhile, must compile on 8.3.5: the lower boundary is set by it, not by the server.

Related MCP server: 1C_MCP_SERVER_OWN

Contents

  1. Running — Docker, dashboard, link graph, without Docker

  2. Connecting an agenthow MCP works, if it does not connect, token, client configs: Claude Code, Codex CLI, Cursor, VS Code, Qwen Code, stdio

  3. Toolscall order, sources, query language, platform versions, help merging, replacements

  4. Data management — sources, configuration dump into files, dictionary and search keys, CLI, benchmark stand, server manually, where to get data

  5. How it is built — modules, measurements, tests

  6. Security — tokens, what is open without them

  7. Documents

  8. License — Apache 2.0, relation to the "1C" company


1. Running

Docker (main method)

# 1. Положить исходные данные
mkdir -p data/bootstrap
cp ВыгрузкаКонфигурации.zip                     data/bootstrap/
cp /opt/1cv8/8.3.27.2130/shcntx_ru.hbk          data/bootstrap/

# 2. Поднять
docker compose up -d --build

# 3. Проверить
curl http://localhost:5001/health
{"status":"ok",
 "configurations_total":2,
 "syntax_loaded":true,
 "query_language_loaded":true,
 "configurations":["РозницаДляКазахстана","ОтраслеваяКонфигурация"],
 "syntax":["8.3.5.1570","8.3.23.1997","8.3.27"]}

The platform help and the query language are different sources and different fields: syntax_loaded refers only to the former, syntax lists the versions of the loaded help. Configuration names and help versions are returned only to a request that passed the read check; without a token, status, the counter and two flags remain.

Everything in data/bootstrap/ is indexed at startup: *.zip — configuration dumps, *.hbk — platform help. The same file is not parsed twice: verification is by hash.

A configuration dump into files placed in bootstrap/ out of habit does not fit — the server recognizes it by the archive contents (code is present, the manifest.json / manifest.xml manifest is not) and does not try to parse it: these are gigabytes, and running them at every startup is not possible. Instead of an error, the startup message list names the file with a reminder of where to put such files — data/incoming/, section “Configuration dump into files” below.

The ./data directory is mounted into the container as /data. Inside it the server keeps sources, indexes, cache and registry.json; paths in the registry are relative, so the directory can be moved between the developer machine and the container.

data/ is entirely outside git — it is a volume, not part of the repository. It is moved by copying the directory. Therefore, after cloning, the help must be placed by yourself: the repository does not contain it and cannot contain it — this is content of the "1C" company.

After changing the code, the container must be recreated, not restarted:

docker compose up -d --build --force-recreate

restart will bring up the old container on the old image, and the edits will not apply.

About the port. The server is exposed externally on 5001, inside the container it listens on 8000 — the 5001:8000 forward is in docker-compose.yml. All addresses in this file are external, that is, 5001. If it is taken by another service — change the left side of the forward, do not touch the right side: EXPOSE and the image healthcheck depend on it.

Dashboard

http://localhost:5001/ — six pages:

Page

What is there

Overview

what is loaded: objects, links, platform version, warnings from manifests

Sources

list of loaded items, three coverage tables for each code corpus, path to the full JSON log, loading .zip and .hbk with a transfer bar, deletion, the "Incoming dumps" block — files from data/incoming/, parsing by button

Queries

running a list of wordings with an assessment and the reason for ranking

Links

object neighborhood graph as a picture

Card

object composition or a platform item description — the same thing the agent sees

Dictionary

rules with provenance; add an alias or a synonym group

Loading — what is visible and when

Loading happens in two stages, and they are shown differently.

File transfer — with a bar on the "Sources" page: percentage, volume, speed and estimated remaining time. The browser counts it, and this is not a whim. The server does not see the transfer progress: await request.form() returns control only when the body has arrived in full, and the job is created only after that. Verified on a live socket: 40 MB took 8.1 s, and during that time 32 polls of /sources from a second connection did not see a single job. On localhost there is no difference; on a remote server this is minutes of an empty screen.

At the same time, the 500 MB limit is checked before sending: previously an oversized file was uploaded in full and only then got a refusal.

Parsing — with the "Loading" table on the same page: "accepting" → "parsing" → "ready" or "error" with a reason. While the work is in progress, the page refreshes itself (meta refresh, every 2 s).

The bar requires JS, the table does not. With JS disabled, the form works like a regular one: the file goes out, the job is created, the state is visible in the table — only the bar during transfer is missing.

/graph draws the object neighborhood: color by kind, arrow by link direction, edge label on hover. Clicking a node builds a graph around it, dragging moves it, the wheel zooms. The neighbor limit is chosen on the page (15…400), the truncation is named by a number — "shown 30 of 102".

It answers "what breaks if I touch this": a register surrounded by orange documents immediately tells who moves it.

Depth is always one step. At two steps from a common catalog, a thousand objects are reached, at three — a third of the configuration; further, the link goes through common mechanisms like additional attributes, which connect almost everything to everything. Cutting them off with a threshold by the number of links is impossible: such a node has 34 of them, while a meaningful Справочник.Пользователи has 323. Therefore, a human expands nodes, not a heuristic — they see where it is not worth going.

The agent intentionally has no such tool. In two steps the graph grows to a thousand objects, and common mechanisms cannot be cut off with a simple threshold: a meaningful node may have more connections than a technical one. A separate tool will only be returned together with a measurable output-limiting rule; for now, the exact next step is chosen by a human in the interactive graph.

A miss is fixed without leaving the browser: on the requests page, each phrase has a link "not that — create an alias", which leads to the dictionary with the phrase already filled in. The edit takes effect immediately — indexes are not rebuilt, no restart is needed.

Reads are closed with API_TOKEN, writes with ADMIN_TOKEN. While API_TOKEN is not set, anyone who can reach the address can read — including the structure of configurations and refinements. This is acceptable for localhost, but not for a server on a network.

The tokens are separated because the read token lives in every MCP client's config and leaks along with it; the agent should not have the right to delete sources. The admin token also works as a read token — there is no need to keep two headers.

// .mcp.json — как клиент передаёт токен
{"mcpServers": {"1c": {"type": "http", "url": "http://localhost:5001/mcp",
                       "headers": {"X-Api-Token": "..."}}}}

ASCII only: HTTP headers are encoded in latin-1, Cyrillic will not get through them. /health remains open for healthchecks, but it only returns configuration names when given a token.

Loading, deleting, and editing the dictionary require ADMIN_TOKEN — the same one as for /admin/reload; without it these endpoints do not exist, rather than "they are closed". The token is entered once in the form; what goes to the browser is not the token but a session identifier.

It is set via .env next to docker-compose.yml — a template with all variables is in .env.example:

cp .env.example .env
python3 -c "import secrets; print(secrets.token_urlsafe(32))"   # значение
docker compose up -d --force-recreate

The name in the results is a link to the card: for an object this is the requisites with types, tabular sections, and movements; for a platform element — the signature, parameters, availability, and version of introduction. The same text the agent receives, with a brief / fields / full toggle. A requisite has no card of its own — the link leads to the owner object.

The "Requests" page answers the question "why did the server return exactly this": next to each hit there is a reason — exact match, alias from dictionary, all query words. From it you can see how to treat a miss — with a synonym, an alias, or a weight.

Parsing the help takes a few seconds: the page responds after it, but MCP clients are not delayed — indexing goes into a separate thread.

Without Docker

From a repository copy — the same way tests and CLI are run:

python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
PYTHONPATH=src .venv/bin/python -m mcp1c.server --host 0.0.0.0 --port 5001

Or as a package — then PYTHONPATH is not needed, and three commands appear alongside:

pip install .

mcp1c-server --host 0.0.0.0 --port 5001   # = python -m mcp1c.server
mcp1c reg-list --data data                # = python -m mcp1c.cli
mcp1c-bench --help                        # = python -m mcp1c.bench

Module launch (python -m mcp1c.server) remains working in this case too: the commands are the same entry points under a different name. All keys are described in the section «Data management».


2. Connecting the agent

The server implements the MCP protocol with the standard transports of the official SDK, so it suits any MCP client. No wrappers around HTTP are required.

Transport

When

Address

streamable-http

server in Docker or on a separate machine

http://address:5001/mcp

stdio

the client starts the process locally itself

Both supported transports are verified with the official MCP client: the initialize handshake, tools/list, tools/call, protocol 2025-11-25.

The legacy HTTP+SSE transport is not supported: --transport sse is rejected as an invalid value before reading data and starting the server. An old client needs streamable-http mode or a local launch via stdio.

How it works

It is useful to understand before something fails to connect. The address is one — /mcp, there is no per-tool endpoint; which tool is called is written in the request body, not in the path.

From here on there are two different mechanics, and they should not be confused:

Tool descriptions

Data

When

once, at connection

on every call

Who initiates

the client, itself, without the model

the model, by decision

Method

POST initialize, then POST tools/list

POST tools/call

Where it goes

the model's system prompt

the conversation body

Cost

one-time, sits for the whole session

per call

On connection, the client makes POST initialize — the server responds with the name, version, and text of instructions, and returns mcp-session-id in the header. Then POST tools/list returns the tools all at once: name, description, JSON parameter schema. All of this goes into the model's context before the human has typed the first word. The model does not go for the description when it needs it — it already has it.

Hence a consequence important when editing descriptions: they take up space in the window for the whole session, regardless of whether the model calls a single tool or none.

There are always ten tools, regardless of what is loaded. The set is a contract, not a variable: the tools are interconnected, and on a working server the sources of configuration, code, platform help, and query language can be loaded independently. The size of the tools/list contract plus instructions does not depend on the state of the registry.

From this follows a direct consequence you need to know in advance: if a source is not loaded, you still pay for its tools. Without platform help, search_syntax and get_syntax sit in the context and cost 1,185 tokens, answering "help is not connected"; compare_configurations with one configuration — 262 tokens for the answer "at least two are needed". This is treated by loading the source, not by filtering tools: a dynamic set was tried on 2026-08-19 and abandoned, because independent sources must keep a single session contract.

Descriptions are therefore written densely, and details go into the tool's own output: you pay for them only when they are needed.

GET /mcp is not a "wrong POST", but a third method on the same address: it opens a message stream from the server to the client and requires an already obtained mcp-session-id. DELETE /mcp closes the session.

If the client does not connect

The response code in the log (docker logs -f mcp1c) names the reason:

Code

What is wrong

406

the client does not send Accept: application/json, text/event-stream — both types are needed

400 Missing session ID

the client did not return the mcp-session-id header obtained at initialize

400 on the very first GET /mcp

the client started the handshake with GET — it speaks the old HTTP+SSE transport, while streamable-http is on this address

404 on /sse

the same thing: the old transport is completely disabled

401

X-Api-Token was not passed when API_TOKEN is set

empty in the log

the client did not send a request at all — the matter is in its config, it never reached the server

A real case: Qwen Code did not connect because its config had the key url — in the Gemini CLI family (Qwen inherits the format) it means the old SSE transport, and the client started with GET, getting 400. With httpUrl — that is, streamable-http — the connection succeeds immediately.

Token: what to add to client settings

If API_TOKEN is set on the server, every client must send it as a header. Without the header, /mcp responds 401, and the agent simply will not see the tools.

Either of the two headers works — the server accepts both:

X-Api-Token: <токен>
Authorization: Bearer <токен>

Three things people stumble over:

  • ASCII only. HTTP headers are encoded in latin-1, a Cyrillic token will not get through them. Generate like this: python3 -c "import secrets; print(secrets.token_urlsafe(32))".

  • The client gets API_TOKEN, not ADMIN_TOKEN. The admin one will also be accepted, but the client config goes into git and backups: a leaked read token gives viewing, a leaked admin one — the right to delete sources.

  • stdio requires no token at all. There the client starts the process itself, the network is not involved and there is nothing to check. If the client cannot set headers — this is a working workaround.

To check that the server sees the token, before any client setup:

curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  -H 'x-api-token: ВАШ_ТОКЕН' \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}' \
  http://localhost:5001/mcp

200 — the token is accepted. 401 — wrong token or the header did not get through.

How not to commit a secret

.mcp.json and similar files usually live in the repository. Options:

  1. Variable substitution — if the client supports it (Claude Code does): "X-Api-Token": "${MCP1C_API_TOKEN}", and the variable itself in ~/.zshrc. What goes into git is the variable name, not the value.

  2. Move the file out from under git: git rm --cached .mcp.json && echo ".mcp.json" >> .gitignore.

  3. Keep the setting not in the project but in the client's user config — then the repository is not involved at all.

Claude Code

The file .mcp.json in the project root:

{
  "mcpServers": {
    "1c": {
      "type": "http",
      "url": "http://localhost:5001/mcp",
      "headers": { "X-Api-Token": "${MCP1C_API_TOKEN}" }
    }
  }
}

The headers block is needed only if API_TOKEN is set on the server. The value is taken from an environment variable so the file can be kept in the repository:

echo 'export MCP1C_API_TOKEN=ваш_токен' >> ~/.zshrc && source ~/.zshrc

Or by command:

claude mcp add --transport http 1c http://localhost:5001/mcp \
  --header "X-Api-Token: $MCP1C_API_TOKEN"

Codex CLI

~/.codex/config.toml or .codex/config.toml in the project:

[mcp_servers.mcp1c]
url = "http://localhost:5001/mcp"

# Только если задан API_TOKEN. Имя ключа для заголовков у Codex менялось между
# версиями — сверьтесь со своей (`codex --help`, раздел MCP). Не подхватилось —
# используйте stdio, там токен не нужен вовсе.
[mcp_servers.mcp1c.http_headers]
X-Api-Token = "ваш_токен"

Cursor

.cursor/mcp.json:

{
  "mcpServers": {
    "1c": {
      "type": "streamable-http",
      "url": "http://localhost:5001/mcp",
      "headers": { "X-Api-Token": "ваш_токен" }
    }
  }
}

VS Code (Copilot)

.vscode/mcp.json — here the key is called servers:

{
  "servers": {
    "1c": {
      "type": "http",
      "url": "http://localhost:5001/mcp",
      "headers": { "X-Api-Token": "ваш_токен" }
    }
  }
}

Qwen Code

The format is from Gemini CLI, and the key chooses the transport — this is the only subtlety:

{
  "mcpServers": {
    "1c": {
      "httpUrl": "http://localhost:5001/mcp",
      "headers": { "X-Api-Token": "ваш_токен" }
    }
  }
}

httpUrl — streamable-http, our case. url in this format means the old SSE transport: the client will start the handshake with GET /mcp, get 400 Missing session ID, and will not connect.

Other clients

Windsurf, Antigravity, Cline, Roo Code, console agents — the record format is the same: transport type, URL, and, if API_TOKEN is set, a headers block. The differences are only in the file name and in the top-level key (mcpServers or servers) — check the documentation of the specific client.

The client cannot set headers — not a dead end: connect via stdio, where no token is needed because there is no network.

Local launch via stdio

When the client must start the server itself. No token is needed here: the process is started by the client, communication goes through the process channels, not through the network — there is nothing to check and nothing to defend against.

{
  "mcpServers": {
    "1c": {
      "command": "python3",
      "args": ["-m", "mcp1c.server", "--transport", "stdio", "--data", "/путь/к/data"],
      "env": { "PYTHONPATH": "/путь/к/проекту/src" }
    }
  }
}

3. Tools

The set is fixed and deliberately small: each tool constantly hangs in the agent's context. A new tool appears only for a separate user task, not for every internal index.

Tool

Purpose

list_configurations

what is loaded, which providers are available for each configuration

search_objects(query, config, kind, limit)

human wording → exact object name

search_procedures(query, config, extension, scope, limit)

exact name or words → procedures in the configuration code or a single extension

get_procedure(address, config, extension, start_line, lines)

module table of contents or signature, compilation context, and procedure body window

get_callers(address, config, extension, limit)

confirmed call sites, metadata bindings, and form handlers

get_object(full_name, config, detail)

object composition; detail: brief / fields / full

get_related(full_name, config)

movements, references, dependencies — direct only

compare_configurations(full_name, configs)

one object in two configurations

search_syntax(query, config, kind, limit)

search across the platform help and the query language

get_syntax(name, config, detail)

signature, parameters, availability, version, replacement for the old platform

config is required when more than one configuration is loaded: the server deliberately does not fill it in silently — otherwise the agent would write code against someone else's database without anyone knowing.

list_configurations shows the actual state of the code index for the main configuration and each extension: ready, building with stage and progress, error, or code not loaded. For a ready extension it outputs the number of overlaps across the four annotations, the count of affected modules and own procedures, and a ready extension parameter for addressing its code. A ready corpus with an unknown or corrupted variant is called the "ready with restrictions" state. All three shells receive exact aggregates of a single generation but display them according to their own task. list_configurations and reg-list keep detailed diagnostics: categories, the first 20 problems in stable order, and the exact count of the remainder; for an unaddressable candidate only the ordinal number is output, without the physical name and local path. /sources instead of an address feed shows three cross-checkable tables with count, percentage, and explicit denominator: modules and procedures, form structures, form modules.

The full list of each corpus is atomically stored in a single current file data/logs/code-<sha256 source_id>.json. The main configuration and each extension have different logs; a reload replaces only its own file, deleting a source removes its file. A log that is broken, from a different generation, or substantively diverging from the current indexes is not shown as current and is rebuilt from the ready indexes. Form structures separately show how many are fully, partially, and not read. The contract and invariants of schema v1 are described below. The warning stands above an incomplete answer and states directly: a zero counter does not prove the absence of hidden data.

Code coverage log — schema v1

One file belongs to one independently removable source <Configuration>:modules or <Configuration>:ext:<Extension>. The source name is not included in the path: the file is named by the full sha256 source_id.

{
  "schema_version": 1,
  "kind": "module_coverage",
  "source": {
    "id": "Отраслевая конфигурация:modules",
    "kind": "modules",
    "sha256": "...",
    "loaded_at": "2026-08-24T12:00:00+00:00",
    "selection_version": 4,
    "locator_generation": 3,
    "code_version": "1.0.0"
  },
  "identity": {
    "source_id": "Отраслевая конфигурация:modules",
    "source_sha256": "...",
    "generation": 3
  },
  "coverage": {
    "modules": {
      "total": 2,
      "source_available": 1,
      "empty": 0,
      "partial": 0,
      "unreadable": 0,
      "conflict": 0,
      "compiled_without_source": 1
    },
    "procedures": {"total": 1, "full": 1, "partial": 0},
    "form_structures": {
      "total": 1, "full": 0, "partial": 1, "unavailable": 0
    },
    "form_modules": {
      "total": 1, "read": 1, "empty": 0, "missing": 0, "unreadable": 0
    },
    "limitations": {
      "categories": {"compiled_without_source": 1, "unknown_marker": 1},
      "occurrences_total": 2,
      "problem_rows_total": 2,
      "unknown_markers": 1,
      "known_markers_incomplete": 0,
      "unsupported_addresses": 0,
      "broken_containers": 0,
      "unreadable_bodies": 0,
      "budget_exceeded": 0,
      "body_conflicts": 0,
      "compiled_without_source": 1
    }
  },
  "problems": [
    {
      "category": "unknown_marker",
      "address": "ОбщаяФорма.Пример",
      "ordinal": 0,
      "reason": "маркер form не поддержан",
      "marker": 99
    },
    {
      "category": "compiled_without_source",
      "address": "ОбщийМодуль.Пример",
      "ordinal": 0,
      "reason": "скомпилированный модуль без исходника",
      "marker": null
    }
  ]
}

The categories modules, procedures, form_structures, and form_modules must in total equal their total; both form tables have one denominator; problem_rows_total equals the length of the full problems. address is null if a canonical address cannot be proven, in which case ordinal is the deterministic candidate number. marker stores the read marker of a form structure or null if the problem does not relate to it. identity links the log to the sha256 and generation of the locator catalog.

The file never contains absolute paths, raw exceptions, tracebacks, tokens, BSL bodies, or the raw form record. The catalog is opened without following a symbolic link; a temporary file is created inside it and atomically replaces the final one. A write failure does not cancel the corpus readiness.

get_object adds code information depending on detail: on brief it does not call the code provider, on fields it gives one line with counters, on full it lists the modules and all forms of the object without their contents. A form remains visible by any evidence: a standalone XML descriptor, Form.xml, Form.bin, a flat .Form, or a single form module. If the index is building, finished with an error, or not loaded, the object metadata is still returned together with the honest code state. get_related marks related objects whose procedures are actually overlapped by any loaded extension; an extension's own procedure does not create such a mark. On fields and full the object card lists, without a common limit, the reasons related to its forms, so an unread form does not turn into a false "no forms". The procedure and relation tools also place a warning above a partial result; a generation change repeats the entire answer together with counters and problems.

search_procedures combines two levels. An exact name match finds any procedure, including non-exported ones; search by words, inflections, and the header comment works only on exported ones. At each level limit is set separately and must be an integer from 1 to 50. The response includes the address Module::Name, the signature from disk, export status, line number, the number of confirmed call sites, and a separate counter of same-name sites whose target could not be resolved. The boolean index of the extension annotation is called neutrally: it does not pass off &After or &Before as a full overlap of the original procedure. Signatures and bodies are not kept in memory. Hierarchical .bsl, flat .txt, and the module record of the source containers Form.bin/.Form go through the same lexer; search and the subsequent card use a locator of one generation rather than reconstructing the path from the address.

Without scope the search is global. An explicit scope="Document.CashReceipt" raises all modules of the object, and scope="CommonModule.CommonPurpose" or another exact address — a single module; the rest of the results stay below. The scope is not guessed from query. extension selects exactly one loaded extension; without it the search runs only over the main configuration.

get_procedure accepts an exact address from the search. A module address without :: returns a table of contents without bodies; Module::Name — the signature, compilation context, and body. start_line starts at 0, lines must be an integer from 1 to 200; a long answer contains a ready call for the next window without skipping lines. For the selected extension, &After, &Before, and &Instead show its body; &ChangeAndControl — the body of the main configuration and only the verbatim #Insert/#Delete blocks. The tool warns about other loaded extensions above the body but does not mix their code into the answer. Signatures and bodies are read from disk for a single answer and do not remain in memory. The same locator survives the cache and a restart: if the source is replaced during reading, the entire answer is repeated against the new generation, and parts of two generations are not mixed.

get_callers accepts only an exact Module::Name address. First it groups confirmed call sites by module and names the owner procedure, then shows event subscriptions and scheduled jobs from the metadata graph, after them — the form item and event from Form.xml. limit must be an integer from 1 to 50; after truncation the exact numbers of sites and modules remain. A descriptor or container record by itself does not prove the absence of a handler: with unproven positional semantics the tool reports partial coverage. The Form.xml status is stored separately, so intact XML bindings remain available even with a defect in a neighboring container. Calls to a same-name procedure without a resolved module are output separately and are not attributed to the requested address. If two procedures lie on the same physical line, the owner is not guessed. The tool does not read module texts. An empty answer reminds that string calls through Execute, DescriptionOfAlert, and ConnectWaitHandler are not indexed.

What the provider does not know

  • Word search runs only over exported procedures. A non-exported one can be found by exact name or through the table of contents of a known module.

  • Dynamic calls where the name is passed as a string to Execute, DescriptionOfAlert, or ConnectWaitHandler are not indexed.

  • A compiled common module has no available source: the address itself is visible, but not the procedures, calls, signatures, and bodies inside it.

  • In the container structure Form.bin/.Form, including the flat export, the bracket grammar and the root markers 19, 20, 23, 25, 26, 27 are proven so far, but not the purpose of the positional fields. Therefore such a form is visible as partially read, and its attributes, items, and events are not guessed. The full composition and bindings are still available only from Form.xml; the XML descriptor separately gives the UUID, name, synonym, and form type.

  • A same-name call that is not resolved to an exact module is not attributed to the requested procedure and is shown separately.

One name can live in two domains at once: StrFind exists both in the platform (since 8.3.6) and in the query language. Then get_syntax lists same-name entries with a ready address for each, and the call can be repeated with a string from the output:

get_syntax("СтрНайти")                    → Одноимённых элементов: 2
                                            - `Глобальный контекст.СтрНайти` — Метод, с 8.3.6
                                            - `Запрос.СтрНайти` — Функция запроса
get_syntax("Запрос.СтрНайти")             → карточка функции языка запросов

The Query. qualifier is needed because a query language element has no owner: it cannot be named via Object.Member like a platform one.

Call order — and what is lost if it is violated

list_configurations
├─ search_objects → get_object → get_related
├─ search_procedures → get_procedure → get_callers
└─ search_syntax → get_syntax

In the code chain each step adds information that the previous one lacks. Without search_procedures you need to know the exact address in advance and lose search by purpose and scope. Without get_procedure the signature, compilation context, procedure body, and exact extension semantics are not shown. Without get_callers there is no check of the direct consequences of an edit: confirmed call sites, metadata bindings, and form events.

The get_object step must not be skipped. Search returns only names and counters; everything the code depends on lives in the object card:

  • the type and periodicity of the register. SliceLast exists only for a periodic information register, and non-periodic ones are 566 of 603;

  • ready field names of virtual tables. In a query the resource Quantity is called QuantityRemainder, QuantityTurnover, QuantityReceipt — such names are not visible anywhere in the configurator; the platform generates them;

  • the subconto limit, correspondence, schedule resources — without them there is nothing to name fields like SubcontoDt1;

  • unlimited-length strings. Marked directly in the type: String (unlim. — only via SUBSTRING) versus String(200). Such a field cannot be placed in a query as is — the platform will not let it be compared, grouped, or ordered. Such fields make up 23% to 38% of string fields in live configurations, so on cards where they occur (2,474 of 20,522), before the field list a disclaimer with a recipe is printed.

A query written right after search_objects looks correct and fails with "field not found". An example of what comes only from get_object:

## Таблицы запроса
- `РегистрНакопления.ТоварыНаСкладах.Остатки`
  измерения: Склад, Номенклатура, Характеристика
  ресурсы: КоличествоОстаток, РезервОстаток

A second one like it — and it was found by a live miss on 2026-08-18. The agent grouped the query by an unlimited-length string; we returned correct data, but the difference was readable only by the absence of the number in parentheses:

> **Строки неограниченной длины** помечены `(неогр.)`. Платформа не даёт их
> сравнивать, группировать и упорядочивать и не пускает в РАЗЛИЧНЫЕ,
> ОБЪЕДИНИТЬ и агрегатные КОЛИЧЕСТВО, МИНИМУМ, МАКСИМУМ. Ограничивайте
> длину — одинаково в списке выборки и в группировке:
>
>     ПОДСТРОКА(КодСкидки, 1, 100) КАК КодСкидки
>
> Длину подбирайте по смыслу поля: 100 — не универсальное число.

## Реквизиты

- `КодСкидки` — Строка (неогр. — только через ПОДСТРОКА) // Код скидки
- `КодМаркировки` — Строка(200) // Код маркировки

The caveat appears both in the note and in the field's own row, and that is not redundancy. The first revision printed the caveat as the last paragraph of the card. A live agent on 2026-08-18 called get_object with detail=fields, received it in full — and still grouped by that field. The caveat sat 721 tokens after the field row, and the decision is made where the name is copied. The same lesson had already been recorded on tool descriptions: a rule works where it is read, not where it is tidiest to put it.

Each prohibition has been verified: aggregate ones — a quote from the help, the other five — runs against a live database with recorded error texts. The help knows about the restriction in only three aggregate functions out of six and says nothing about grouping, ordering, DISTINCT, UNION, and comparison — meaning an agent that honestly read it could not have learned about it. The breakdown by origin is in docs/data-sources.md, section "Caveats in the card".

Before calling a platform function on an old configuration — get_syntax. What is unavailable is flagged, and the replacement recipe, if recorded, sits right there.

Sources are independent

There are five of them, and each is attached separately:

Source

File

What it provides

Without it

Configuration metadata

СтруктураКонфигурации_*.zip

objects, attributes, links, movements

search_objects and get_object do not respond

Configuration code

configuration dump-to-files archive

procedures of the main configuration, signatures, bodies, compilation context, call sites, form events

without configuration code, search_procedures, get_procedure, and get_callers name the missing source

Extension code

extension dump-to-files archive

the selected extension's own and modified procedures, their bodies and call sites

without extension code the main configuration is available, but search_procedures, get_procedure, and get_callers do not see that extension's code and overrides

Platform help

shcntx_ru.hbk

methods, properties, signatures, availability, versions

search_syntax says "source not attached"

Query language

shquery_ru.hbk

SELECT, LEFT JOIN, TOTALS BY, DATEDIFF

query language constructs are not found

What is loaded

What works

All required sources

everything

Configuration only

metadata; syntax replies "source not attached"

Help only

syntax without version filtering, config not needed

Nothing

list_configurations explains what to load

Query language is a separate source

shquery_ru.hbk from the same platform installation directory. 127 pages: 52 functions, 67 keywords, 8 articles. It loads like a regular source and lands in the same search index as the platform help — no separate tool is needed to search it, search_syntax finds both.

There are no versions in the file itself — verified across all 129 pages: zero mentions of "8.3.x" and "as of version". But the query language does change: release 8.3.20 added 25 functions, among them StrFind, Left, Right, InReg, NReg, StrReplace, Round, Int, and all of trigonometry.

There is nowhere to take the version from: the platform help does not describe query language functions at all (SUBSTRING — zero matches across 25,511 items). So versions are set by the curated table query_versions.py — based on 1C's list "Functions added to the query language as of release 8.3.20". The remaining 27 functions get no version: they have always existed.

The usual filter then applies: a configuration on 8.3.5 will not see these functions, one on 8.3.23 will.

The table is verified against data — by comparing two help files from different platforms. What is absent in the old one and present in the new one appeared in between, and it must have a version:

python3 tools/lab/compare_query_help.py <старая.hbk> <новая.hbk>

Run on 2026-08-19, 8.3.5.1570 against the current one: 29 appeared, 29 covered, 0 false positives. A false positive is the worst kind of error: an item that was already in the old help but is marked with a version will hide from a configuration where it exists.

There is one instance per server: a repeated load replaces the previous one.

Page tables are displayed but not searched. In this help, table cells are marked up as paragraphs inside <TD>, and without separate parsing the card printed a table as a column of values: "Product / Quantity / Number / Plumbing / 104 / …" two dozen rows in a row. Now tables are parsed into a separate field — 51 tables across 31 of 127 pages — and printed in their places in the text: a page with two examples shows each result under its own example. Table contents do not enter the search index.

The tables in this help are of two different natures and are parsed differently:

What

How many

How it looks in the card

data table — the result of an example query

51 across 31 pages

as a markdown table

drawn syntax diagram — the grammar of a construct

21 across 17 pages

as a staircase indented by branching level

They differ by markup, not by CSS class: class=SimplyTable is not on all of them — 7 real tables go without it. The distinguishing feature is geometry: in a data table all rows have the same width, in a diagram the widths are ragged and there are cells made of a single vertical bar (that is a drawn line, not a value).

Corrupted markup is named out loud. A page with an unclosed <TABLE> is parsed without tables but is not lost, and its name lands in the source's warnings: as a line in the load output (mcp1c.cli reg-add) and as a separate line on the "Sources" page of the dashboard. Silently handing out a card poorer than usual is not allowed: that would be indistinguishable from help that simply does not have it.

Half the names match platform names (57 of 127) — YEAR, MONTH, REPRESENTATION exist in both. So that a question about a query does not lead to a platform method, phrasings like "in a query", "in the query text", "in a selection" give query language items a soft boost. Soft on purpose: with a confident lead, the platform item stays first — "how to set a parameter in a query" can also be about Query.SetParameter.

The config parameter is mandatory if more than one configuration is loaded. Nothing is substituted by default: a silent choice leads to the agent writing code against someone else's configuration, and nobody notices.

The answer depends on the platform version

The same call, two configurations:

get_syntax("СтрШаблон", config="Розница")     → 8.3.23
# Метод: Глобальный контекст.СтрШаблон
с версии платформы 8.3.6
Доступность: ТонкийКлиент, ВебКлиент, Сервер, ТолстыйКлиент, …

get_syntax("СтрШаблон", config="Отраслевая")  → 8.3.5
# `Глобальный контекст.СтрШаблон` недоступен в этой конфигурации
Элемент существует, но появился в 8.3.6, а конфигурация работает на 8.3.5.1570.
Использовать нельзя — код не скомпилируется.

For platform 8.3.5, 6,539 items were removed from the output, for 8.3.23 — 874. Not by warning but by filtering: the agent will skip a warning, but a method absent from the output — no.

The Availability field (server / thin client / web client / mobile) must be read: calling a server method from a client context does not compile.

Help files of several versions merge into one index

A single fresh help file on an old configuration lies. Measured on 8.3.5: 199 items the server would declare nonexistent, 117 it would return with a foreign signature (XMLWriter.OpenFile on 8.3.5 takes two parameters, on 8.3.27 — three), 410 — with foreign availability. All of these are compilation errors, not inaccuracies.

So help files of different versions are placed side by side and merged into one index with since and until boundaries, and the answer is assembled for the version of the specific configuration. You need as many help files as there are platforms among the loaded configurations — two extreme ones do not replace the intermediate ones.

The cost is measured and small: merging three versions yields 25,691 keys versus 24,777 for one, that is, less than a percent. A separate container per version also works and remains the fallback path, but as the primary one it lost on the numbers — 300–450 MB and its own address for each version.

The server itself names which help files are missing and which are extra — in the list_configurations output.

Replacement instead of prohibition

Saying "the function does not exist" is half the answer. The other half is what to replace it with, and that cannot be derived from the help: the deprecation mark sits on 15 pages out of 25 thousand.

So there is a replacement table (replacements.py), currently 6 entries — string functions that appeared in 8.3.6. Instead of a prohibition, get_syntax returns a recipe:

get_syntax("СтрРазделить", config="Отраслевая")   → 8.3.5
# `СтрРазделить` недоступна: появилась в 8.3.6

Замена: РазложитьСтрокуВМассивПодстрок(<Строка>, <Разделитель>)
Оговорка: разделитель у `СтрРазделить` — набор символов, каждый из которых
самостоятельный разделитель; у замены это одна строка целиком.

The caveat is mandatory. A replacement is almost never equivalent, and silently slipping in a similar function is worse than suggesting nothing.

The table is filled from live cases, not blindly: inventing workarounds for functions nobody has asked about makes no sense.


4. Data management

Add a source

# в Docker
docker compose exec mcp1c python -m mcp1c.cli reg-add /data/bootstrap/Выгрузка.zip --data /data
docker compose exec mcp1c python -m mcp1c.cli reg-add /data/bootstrap/shcntx_ru.hbk --data /data

# без Docker
PYTHONPATH=src python3 -m mcp1c.cli reg-add Выгрузка.zip

Simpler: put a file in data/bootstrap/ — it will be picked up at the next start.

Configuration dump to files

The second kind of source is not metadata (СтруктураКонфигурации_*.zip) but the configuration dump to files itself: module and form code. The server builds and caches internal indexes of procedures, calls, and forms; search_procedures searches against the ready set. On a cache miss, the indexes are built in the background: tools that do not depend on code keep responding, while code tools show stage X/4 and the actually processed N of M items of the current stage. The ready set is published as a whole, without an intermediate state from different versions. Background building does not overwrite registry.json while startup is still restoring the next sources. If startup terminated abruptly, the previous full snapshot on disk remains intact; a partially restored source list is not saved on top of it.

Four expendable cache files keep safe locators, coverage aggregates, and the first 20 anonymized problems with the exact count of the rest. Module bodies, procedure signatures, and the raw form record do not go there. On a warm start, the source's sha256, the selection version, the locator generation, each index's internal invariants, and their consistency with a single directory are checked. Therefore a structurally or semantically damaged set counts as a miss as a whole, not as a source of a partially stale answer. A damaged or read-only cache does not bring the source down: the server performs a cold rebuild in memory and publishes full diagnostics, even if the new cache cannot be written. Published cold and warm answers match on all counters and reason categories. Local reasons for each form are stored separately from the limited list: thanks to this, get_object after a restart shows all form problems of the selected object without turning the general /sources into an unlimited response. The reader does not read a cache file larger than 256 MiB and does not unpack a payload larger than 512 MiB; exceeding either limit is an ordinary cache miss before marshal.loads, not an attempt to exhaust memory.

Final live acceptance of selection v4 was completed on 2026-08-21 on four anonymized code sources (three main exports and one extension):

PYTHONPATH=src .venv/bin/python \
  tools/lab/measure_modules_acceptance.py --data data --timeout 120

The script prints a single JSON without names or paths. Warm startup took 3.676 s, startup_problems_total = 0, process peak — 856.2 MiB. The catalog proved 8,650 hierarchical .bsl files, 828 modules from Form.bin, 1,523 flat .txt files, 1,079 modules from .Form, and 7 compiled modules without source. 6,150 forms were found: 3,331 fully, 1,330 partially, and 1,489 not read. Exact constraint categories: descriptor_only=671, invalid_syntax=1598, known_marker_semantics_incomplete=659, form_structure_missing=561, compiled_without_source=7; there are no unknown markers, budget overruns, broken containers, or conflicts in this corpus. Zero here is the result of measuring precisely these sources, not a promise of support for any future format.

Previous background responsiveness measurement on 2026-08-21 on a cold cache of three code sources: in 72.12 s, 73 one-second calls of each path were made, no failures. This timer starts after /health readiness, whereas the container measurement of 125 s below goes from container start to ready of all code sources. /health responded with a median of 5.76 ms and a worst time of 171.01 ms; the real search_objects — 8.11 and 172.49 ms respectively. The container reached healthy, all 12 cache files were published. To repeat, mount a copy of data/ without index/cache/*.modules-* into a separate container and run:

.venv/bin/python tools/lab/measure_background_responsiveness.py \
  http://127.0.0.1:5002 /путь/к/копии/data

Memory of the final image was re-measured on 2026-08-21 after selection v4: four code sources and 16 module index cache files. Cold startup after removing only the expendable cache took 111.216 s, warm restart of the same container with unchanged dev, ino, size, and mtime_ns of all 16 files — 11.495 s.

State

memory.peak cgroup

memory.current after ready

RSS PID 1

HWM PID 1

docker stats

cold rebuild

1 404 649 472 B (1 339.6 MiB)

1 400 582 144 B (1 335.7 MiB)

762 180 KiB (744.3 MiB)

764 228 KiB (746.3 MiB)

728.3 MiB

warm start

612 851 712 B (584.5 MiB)

599 142 400 B (571.4 MiB)

570 180 KiB (556.8 MiB)

570 180 KiB (556.8 MiB)

533.2 MiB

Both times the container reached healthy; restart_count = 0, oom_killed = false. In each of the two final runs there were exactly 0 lines with each of the terms traceback, exception, critical, and error. cgroup values include the kernel page cache, so they naturally differ from the process RSS and the docker stats reading. This is a reference point for the size of precisely this corpus, not a basis for assigning mem_limit.

The script prints a single JSON with only aggregates: duration, number of sources and caches, cgroup and PID 1 fields, docker stats, health, restarts, OOM, and journal term counters. Source names, local paths, and journal lines are not printed. In cold mode it builds the final image, first stops the container, and removes only the exact files modules-toc, modules-calls, modules-forms, and modules-search, computed by the working name function for the current sources. The index/cache directory and the targets themselves cannot be symbolic links; an extra or mixed set of names is not considered ready. After stopping, the directory is opened with O_NOFOLLOW, and its dev/ino are verified against the initially checked directory. Targets are listed and verified via dir_fd; only exact base names are removed through the same descriptor: replacing the parent with a symbolic link does not lead outside, and a hard link loses only the internal link. Then the script does --force-recreate. The measurer itself does not remove or replace sources, unpacked code, or registry.json. A running server routinely atomically updates registry.json states during startup and background build; the measurement accounts for these records in the generation marker. Readiness requires not only healthy, but also unchanged sha256, loaded_at, and the binding of the code source itself, the same sha256 of the owner configuration, the ready state, and the exact set of four caches per source. On a normal restart, configuration metadata is parsed again and gets a new runtime loaded_at; the measurer allows only this transition, and then requires two identical snapshots of the new generation. After reading memory, docker stats, and the journal, the final check reads the registry, then the cache, and again the registry; both markers must match the ready generation. The marker also includes dev, ino, size, and mtime_ns of registry.json itself, so even writing the same bytes counts as a new generation. In warm mode, before stopping, it remembers dev, ino, size, and mtime_ns of each exact cache file. Immediately after docker stop, before docker start, it reopens the directory with O_NOFOLLOW, verifies its dev/ino and the entire file snapshot through the held dir_fd; the same snapshot is required at readiness and in the final check. A rebuild, overwrite, or corruption of a file invalidates the entire result. --timeout and --poll-interval are finite positive seconds; the single timeout remainder limits each Docker command.

.venv/bin/python tools/lab/measure_container_memory.py --mode cold --data data \
  --timeout 300 --poll-interval 0.5
.venv/bin/python tools/lab/measure_container_memory.py --mode warm --data data \
  --timeout 300 --poll-interval 0.5

The extension export is placed in the same place, in data/incoming/, and is parsed with the same button. The server determines the export type itself, by Configuration.xml inside the archive — the person still selects only the configuration to which the extension is attached, not a separate button or an "this is an extension" field. The extension is created as a separate source <ConfigurationName>:ext:<ExtensionName> (type extension), not <ConfigurationName>:modules: the extension name is taken from the Name tag of its own export, not invented by a person. The code goes into its own directory data/extensions/<ConfigurationName>/<ExtensionName>/ — next to data/modules/<ConfigurationName>/, but not inside it, so a second export does not erase the first. A single configuration can have any number of extensions — while configuration modules still have exactly one export, as before.

The extension's identity is the Name tag inside its export, not the file name and not the entire content. A person can rename the archive however they like: two files with different names but the same Name re-parse ONE and the same source — the second parse simply updates the code and origin (the name of the last parsed file); the key and directory do not change. The flip side of the same rule: if the Name of two DIFFERENT extensions, after cleaning of characters invalid for a path, coincides (for example, Price/Retail and Price:Retail produce the same directory name), they too collapse into one source — the second parse overwrites the code of the first. For ordinary names (without characters outside letters, digits, hyphen, underscore, period, and space), cleaning changes nothing and such a collision does not occur.

Recognition is a positive rule on both sides, and the check order matters. Erring in favor of a configuration is more dangerous: it means going into the modules branch and destroying the already parsed configuration code entirely. Therefore, first they look at strong extension indicators (ObjectBelonging, ConfigurationExtensionPurpose) — if at least one is visible, there is no path to the modules branch under any CompatibilityMode: then the decision is only between "extension" (all four conditions met: both strong indicators, non-empty NamePrefix, absence of CompatibilityMode) and rejection. And only when there is no strong extension indicator at all does CompatibilityMode decide: non-empty — configuration, empty or tag absent — rejection. Everything else is also a rejection with an explanation, without touching anything on disk: Configuration.xml was not found (searched not only in the archive root, but also in the single top-level directory — this is how an archive is built with the zip -r archive.zip folder command, and the service __MACOSX/ with .DS_Store of a Finder archive on macOS does not interfere), cannot be read (broken CRC, truncated record), does not parse as XML, does not carry a recognizable structure, or the declared file size is implausibly large.

Where to put it. In data/incoming/, on the disk, not through the upload form on "Sources" — it has no field for this. The server itself creates the directory at startup, so it is in place from the first run. Then on the "Sources" page (only for someone logged in under ADMIN_TOKEN), an "Incoming exports" block appears with a "parse" button.

Only the directory itself is scanned, without nested subdirectories. An archive placed in data/incoming/Retail/ will not be seen by the server — and it will not say anything about it, because there will be nothing to show.

While the file is being copied, there is no button. cp of one and a half gigabytes takes minutes, and the file is visible in the directory from the first second: for an archive modified less than five seconds ago, the state is shown with the note "file is still being copied", sha256 is not computed at all (it would be outdated anyway), and parsing such a file is rejected with an explanation. Wait for the copy to finish and refresh the page.

Re-parsing overwrites the entire directory — but only after success. For an updated export, the button is labeled "re-parse": the old contents of data/modules/<ConfigurationName>/ (or the extension directory) are replaced with the new one entirely, otherwise files that are absent in the new export (a deleted object, a renamed module) would remain there forever and the two exports would mix. Unpacking goes into a temporary directory nearby and does not affect the previous parse: if extract fails midway (broken CRC of a module, disk full, permissions) or selects zero files, writing to the working directory never happens at all — the registry and disk do not diverge. The replacement is not a teardown of the old in place, but a swap ("rename old aside → rename new into its place → remove the set-aside"): if moving the new into the old's place fails, the server tries to return the old to where it was. If that also fails (the nature of the failure of both renames is usually the same — permissions, the same bind-mount), the parse fails explicitly and names both paths in the text: where the previous parse physically lies and where the empty or partially unpacked new one is, so the directory can be restored by hand; the server will not silently show "parsed" for a directory that does not exist. Remnants of a process interrupted midway (.tmp-*) are removed before the next parse of the same configuration or extension — the saved copy of the previous parse (.old-*), if it came to that, is not removed by itself: the decision about it is left to the person. Re-parsing does not change directory permissions — the new directory gets the permissions of the previous one (or the usual ones, if the directory did not exist yet), not the reduced ones with which the temporary one is created.

Concurrent parses of one source are executed sequentially: a new request immediately cancels the previous generation, but waits for its work with the temporary directory to finish. Therefore, one parse cannot delete another's active .tmp-*, and the working directory and cache are received only by the last generation. A cancelled request ends with an explicit error; a waiting stale request does not start unpacking at all. Different configurations and extensions with different source keys do not delay each other.

"Selection is outdated" — a state for when it's not the file that changes, but the rule. Every parsed source remembers the version of the selection rule (internal SELECTION_VERSION: what we take from the archive and where we put it). If the code on disk was parsed with an old version of the rule, the source is shown as "selection outdated" rather than "parsed" — even though the archive on incoming/ is the same file with the same sha256. The button is labeled "re-parse", as with an updated export: the code on disk is overwritten anew, this time according to the current rule. The previous parse itself is not rewritten — a person sees the selection rule change only after pressing the button. A source parsed before this field existed (a record without selection_version in the old registry.json) is also considered outdated: its version is unknown, not "definitely fresh" — otherwise a person would never see "re-parse" for code that never went through the current rule. The current version 4 preserves XML form descriptors and hierarchical Ext/Form.bin; a version 3 source is therefore necessarily shown as outdated and receives these files only after an explicit re-parse. The selection version is part of the cache stamp: an old cache is not accepted even for the same unchanged ZIP. Before replacing the directory, the server writes a short rotation log; after an emergency stop, the next launch reads registry.json and either completes the new generation or restores the previous directory.

Finder archiver junk on macOS does not get into the code. An archive assembled with the "Compress Objects" command carries a service __MACOSX/ with copies of each file's resource forks (._Name, the same suffix as the original) — selection skips them, as it does any name starting with ._, wherever it lies.

The archive wrapper is not reproduced in the path on disk. An archive packed with the zip -r archive.zip folder command or via Finder ("Compress Objects") — the entire export lies inside a single top-level directory — does not repeat that level when laid out on disk: the code goes to data/modules/<ConfigurationName>/Catalogs/…, not data/modules/<ConfigurationName>/folder/Catalogs/…. The wrapper recognition rule is shared with the search for Configuration.xml inside it (the same place in the paragraph above, "Recognition"): the service __MACOSX/ and the root .DS_Store do not count, and several top-level directories are not considered a wrapper — the server will not guess which of them is "the one". Path sanitization (refusing members that lead outside the root) is applied after unwrapping, not instead of it.

Deleting a source takes the code with it. The "delete" button on a <ConfigurationName>:modules source removes data/modules/<ConfigurationName>/, and on an extension source <ConfigurationName>:ext:<ExtensionName> — only its own directory data/extensions/<ConfigurationName>/<ExtensionName>/: configuration modules and other extensions of the same configuration are not touched. Otherwise hundreds of megabytes would remain occupied invisibly: they are not shown in the "Source files" section, which only has data/sources/.

An archive in which neither modules nor forms were found is rejected. A metadata structure export is also a .zip, and without this check it would create a source with zero files in the "parsed" state. It is submitted via the "Upload" form on this same page.

Why not through the browser. The archive weighs over a gigabyte — for the typical retail sales configuration 2.3.10.5 that is 1,380 MB. The upload form on "Sources" is built for metadata in the range of a few megabytes and fails on such a file for three reasons at once: the upload limit of 500 MB; triple copying with a peak of about 4 GB — the request body receive buffer, a temporary copy in mkdtemp(), and a copy onto the volume itself; and the transfer itself of over a gigabyte over HTTP with no way to resume after an interruption. A file on the volume bypasses all three limits: it is copied with an ordinary cp, and the server reads the archive member by member, never unpacking it in full.

What remains on disk. Not the whole archive — only modules (.bsl for a hierarchical export, .txt and canonical compiled CommonModules/<Name>.Module or CommonModule.<Name>.Module for a flat one) and forms: the XML descriptor, Form.xml and Form.bin for hierarchical, the .Form container in its entirety for flat. Form.bin and .Form are preserved unchanged as a source for the code provider; a derived .bsl is not created on intake. Text templates *.Template.txt are not accepted as modules. The container is saved as is, together with the binary form record: the code of a regular form lies inside it as a module record, and parsing the container on intake would mean dragging 1C format parsing in here.

After unpacking, the physical tree is enumerated once into an immutable catalog of canonical addresses and safe locators. The table of contents, calls, forms, and search all get the same snapshot: ordinary .bsl/.txt files are read as files, Form.bin/.Form code — as the module record of the original container, a compiled .Module remains an address without an invented body. Identical texts of one address are deduplicated after common normalization; different ones do not get silent priority and are excluded as a conflict.

Configuration code goes to data/modules/<ConfigurationName>/, extension code — to its own directory data/extensions/<ConfigurationName>/<ExtensionName>/; current coverage logs — to data/logs/. All three kinds are protected by the **/modules/, **/extensions/, and **/logs/ rules in .gitignore. Measurement python3 tools/lab/measure_intake.py <archive>..., run 2026-08-19:

What

Archive

Unpacked

We select

typical retail sales configuration 2.3.10.5

1,380 MB

2,063 MB, 33,188 files

351.4 MB, 11,072 files

extension to it

1 MB

8 MB, 453 files

6.9 MB, 155 files

A live run of the intake itself on the same files — not an estimate from the zip directory, but a real call to intake.planned_size, intake.enough_space, and intake.extract, measurement python3 tools/lab/measure_intake_run.py <unpack directory> <archive>..., run 2026-08-19: the required space estimate on a 1.4 GB archive — 0.15 s (only the zip central directory is read, the body is not touched at all), unpacking — 3.1 s, process RSS peak — 111 MB. The files and bytes returned by extract() and what actually landed on disk are equal in number — the script verifies this itself.

The server does not delete the source. The data/incoming/ directory is forbidden for the server to delete — the file stays in place even after a successful parse; a re-parse of the same archive does not touch it, verification goes by sha256 against the cache on the page (hashing a gigabyte on every list refresh is not possible).

A source is created under the key <ConfigurationName>:modules, not the configuration name: the same key as metadata would displace them from the registry on upload. Which configuration owns the export is decided by a person. If exactly one is loaded in the registry, no choice is needed: there is no field next to the button on the page, the parse takes the only one itself. If two or more are loaded, a dropdown of loaded names appears next to the button — whichever is selected, the code is bound to that one. The name from the form is verified against the list of loaded ones: an unknown one is rejected before the parse begins, with an explanation. Automatic binding by the export manifest (without human involvement) is not done yet. The platform of a module source is taken from the configuration it is bound to — the export itself does not contain an exact platform build in its files, only the compatibility mode, and that is a different number. For an extension, the configuration choice works the same way (see "An extension export goes to the same place" above); the export kind — modules or extension — the server distinguishes itself by Configuration.xml, there is no separate field or button for this on the page.

Lack of space — refusal before the start. After choosing the configuration and source kind, the registry checks the free space on the volume before unpacking. The required amount is the safely selected files plus an index reserve: 15% rounded up, but no less than 25 MB. The formula is the same for the first upload and a re-parse: the volume occupied by the old root is already excluded from the free space value, it cannot be added again. Not enough — the parse does not start, and the reason with both figures in megabytes stays on the "Sources" page and survives a restart. A direct registry call goes through the same check; on error or volume fill-up, the previous parse stays in place.

A compiled *.Module is visible in the summary and in the module list as CommonModule.<Name>, but it has no procedures or body: search_procedures, get_procedure, and get_callers say so directly and do not pass off an empty index as proof that a procedure is absent.

The "parse" button requires ADMIN_TOKEN — like any other write: without the token this route does not exist, not "it is closed".

Apply changes without a restart

A running server keeps the registry in memory, so after reg-add it needs a nudge. Either a restart (docker compose restart mcp1c, about 2 seconds), or the admin endpoint:

# включается переменной ADMIN_TOKEN; без неё маршрут отключён
ADMIN_TOKEN=секрет docker compose up -d
curl -X POST -H "x-admin-token: секрет" http://localhost:5001/admin/reload

Dictionary: how people speak versus how it is named

The main difficulty of search is the gap between a person's words and the names in the configuration. "Customer order" — but the object is called CustomerOrder. The dictionary lives in data/dictionary.json and is edited without rebuilding the image.

Two mechanisms, and they are different.

Word synonyms — common to all configurations:

python3 -m mcp1c.cli dict-synonyms клиент покупатель заказчик

Object aliases — a direct statement "when I say this, I mean these objects", weight higher than any textual match. Two dozen typical phrases ("files", "goods", "customers", "employees", "tasks") are built in and work right away; if the object is not in the configuration, the alias is not applied. Your own are added bound to a configuration:

python3 -m mcp1c.cli dict-alias "справочник физлиц" \
    Справочник.ФизическиеЛица Справочник.Пользователи \
    --config РозницаДляКазахстана
«справочник физлиц»
    Справочник.ФизическиеЛица     псевдоним из словаря
    Справочник.Пользователи       псевдоним из словаря

Object existence is checked on addition — an alias for a typo is useless. View the contents: dict-show, delete: dict-alias "phrase" --remove.

Changes are applied by restarting the container or POST /admin/reload — no need to rebuild the image. The reload runs outside the event loop, so /health and MCP requests keep being served during recovery.

Search keys of the query language — a third mechanism, and it is edited only in code (search_keys.py) with the usual change review. The gap here is of a different nature: a person does not call a construct by someone else's word, but describes the task. "Number of days between two dates" versus DATEDIFF, "remove duplicates" versus DISTINCT — zero common words, and a synonym will not help, there is nothing to replace.

That is why 116 pages out of 127 have phrasings attached by which they are asked, and they get into the search index as a separate field. At runtime they weigh nothing. Result on a live set: 57.9% → 94.7% first place, no regression on 61 thousand automated queries.

The keys are composed by us, not exported, and from this come three constraints:

  • they live as a separate layer in git, not attached to a parsed element;

  • they do not get into the response to the agent — the response is still assembled only from the help text, the keys work solely on hitting the right article;

  • they are bound to pages by identifier, and if the help produces a different set of pages, the discrepancy is named at load time, not silently manifesting as a degraded search.

The whole rule is in docs/data-sources.md, section "Generated layers over sources".

See what is loaded

docker compose exec mcp1c python -m mcp1c.cli reg-list --data /data
РозницаДляКазахстана  2.3.10.5  платформа 8.3.23.1997
  объектов 5637, связей 44034, загружено 2026-08-18T12:22:16+00:00
  метаданные : да
  синтаксис  : справка 8.3.27, новее конфигурации, скрыто 874
  модули     : не загружен
  язык запросов: подключён, 127 страниц

There may be no configurations at all — the server still works in that case if at least one help is loaded: search_syntax and get_syntax answer, no config needs to be specified. reg-list in this case lists what is connected and returns 0:

Конфигурации не загружены. Подключено:
  язык запросов, 127 страниц
Работают search_syntax и get_syntax, без фильтра по версии.

On a completely empty registry — "Nothing loaded." and exit code 1. Any command that needs configuration will say right there what exactly is missing and where each piece comes from. reg-list does not wait for a cold build: for the main configuration and each extension it shows the same atomic state as /sources and list_configurations — ready counters, stage and progress, an error, or the absence of a code.

Debugging without an agent — mcp1c.cli

The CLI goes to the same registry and the same functions as the MCP tools. If it answers correctly, the problem is in the client setup, not the server.

Commands fall into three groups. Registry — the same thing the agent sees:

PYTHONPATH=src python3 -m mcp1c.cli reg-list  [--data data]
PYTHONPATH=src python3 -m mcp1c.cli reg-add   Выгрузка.zip     [--data data]
PYTHONPATH=src python3 -m mcp1c.cli reg-add   shcntx_ru.hbk    [--data data]
PYTHONPATH=src python3 -m mcp1c.cli reg-search "чек ккм"  --config РозницаДляКазахстана
PYTHONPATH=src python3 -m mcp1c.cli reg-search "разделить строку" --syntax --limit 5
PYTHONPATH=src python3 -m mcp1c.cli reg-search-procedures "провести документ" \
    --config Пример --scope Документ.Чек --limit 10
PYTHONPATH=src python3 -m mcp1c.cli reg-get-procedure \
    'Документ.Чек.МодульОбъекта::ОбработкаПроведения' \
    --config Пример --start-line 0 --lines 200
PYTHONPATH=src python3 -m mcp1c.cli reg-get-callers \
    'Документ.Чек.МодульОбъекта::ОбработкаПроведения' \
    --config Пример --limit 20

reg-search without --syntax searches by metadata; with it, by help text and the query language. Three code commands mirror the eponymous MCP tools and print the same response: reg-search-procedures QUERY accepts --config, --extension, --scope, --limit (default 10); reg-get-procedure ADDRESS--config, --extension, --start-line (0) and --lines (200); reg-get-callers ADDRESS--config, --extension and --limit (20). For all three, --data defaults to data. On a cold one-off run, these commands wait for the background build no more than 90 seconds, so the process does not terminate daemon threads before the result and the writing of four cache files. If the limit was not enough, the command returns an explicit error; the server does not use this wait and keeps showing progress.

Directly from a file, without the registry — inspect an export before it goes to the server:

PYTHONPATH=src python3 -m mcp1c.cli info    Выгрузка.zip
PYTHONPATH=src python3 -m mcp1c.cli stats   Выгрузка.zip
PYTHONPATH=src python3 -m mcp1c.cli show    Выгрузка.zip Документ.ЧекККМ --detail full
PYTHONPATH=src python3 -m mcp1c.cli related Выгрузка.zip Документ.ЧекККМ --depth 2
PYTHONPATH=src python3 -m mcp1c.cli find    Выгрузка.zip реализация --limit 10

The path is a ZIP or an unpacked directory; the format is determined by the manifest.

Search dictionary — synonyms are common, aliases are tied to a configuration:

PYTHONPATH=src python3 -m mcp1c.cli dict-show                       # правила и их происхождение
PYTHONPATH=src python3 -m mcp1c.cli dict-show --all --config Розница...
PYTHONPATH=src python3 -m mcp1c.cli dict-synonyms чек ккм касса     # группа взаимозаменяемых слов
PYTHONPATH=src python3 -m mcp1c.cli dict-synonyms чек ккм --remove
PYTHONPATH=src python3 -m mcp1c.cli dict-alias "справочник физлиц" Справочник.ФизическиеЛица
PYTHONPATH=src python3 -m mcp1c.cli dict-alias "справочник физлиц" --remove

dict-show shows the origin of each rule — that is where the analysis of "why search behaves this way" begins.

Search quality measurement — mcp1c.bench

A separate stand, because "it got better" without numbers is an opinion.

PYTHONPATH=src .venv/bin/python -m mcp1c.bench \
    --data data --config РозницаДляКазахстана \
    --auto --sets query-language,roznica-metadata,modules-procedures \
    --check-notes

Key

What it does

--data path

server data directory, default data

--sets name,name

manual schema v1 sets from tests/queries/*.json, without extension

--auto

automatic sets from help: exact names and eponymous ones

--config

configuration; required if several are loaded

--extension

separate extension code corpus for procedure sets; without the key, base code is measured

--limit

result depth, default 10

--save path

write a run for comparison; by convention data/bench/YYYY-MM-DD.json

--baseline path

compare with a previous run — names by name who changed places

--check-notes

verify notes in the set against the rank the query took

Each manual JSON contains schema_version: 1, an explicit domainsyntax, metadata, or procedures — and a cases array. The file name does not select the index. In a procedure set, expected stores bare BSL names without module names; the stand expands the name case-insensitively into all exact addresses of the selected corpus. This allows publishing reproducible formulations without revealing the structure of a specific deployment. Optional expected_miss: true and zero-based expected_rank machine-verify the recorded baseline with --check-notes; this is not a pytest quality threshold. The old root JSON array and old baselines without a version are rejected explicitly: they need to be recreated. --save first atomically replaces the report file and only then prints results: a write error leaves no partial report either on disk or in stdout.

It prints P@1/P@3/P@5/P@10, MRR, the share of "foreign domain first", and the median gap between the first and second result. There are deliberately no thresholds in asserts: query sets are not tests, percentages would break on every dictionary edit. Exit code 1 means a note mismatch; code 2 means the entire measurement is canceled before printing and saving due to an invalid set, ambiguous selection, or an unready index. When the code changes during a procedure set, the entire set is repeated from the beginning on one index generation.

Comparing two runs looks like this (regressions first):

=== сравнение с прошлым прогоном ===
  - «как прибавить месяц к дате в запросе»: 1 -> промах
  - «как отсортировать результат запроса»: 1 -> 5
  + «в чем разница между внутренним и левым соединением»: 5 -> 4

Sets are not included in the image (tests/ in .dockerignore) — run from a working copy, not from a container.

Server manually — mcp1c.server

PYTHONPATH=src python3 -m mcp1c.server --data data          # streamable-http на :8000/mcp
PYTHONPATH=src python3 -m mcp1c.server --transport stdio    # локальному клиенту
PYTHONPATH=src python3 -m mcp1c.server --host 0.0.0.0 --port 5001

There are no other values for --transport. The legacy HTTP+SSE is fully disabled: the --transport sse flag is rejected before reading data and starting the server.

Where the source data comes from

Configuration structure — by processing from exporter-1c/. Four module variants for regular and managed forms, XML and JSON; XML variants are compatible with 8.3.5. Two processings are already built and open as is: ВыгрузкаСтруктурыКонфигурации_ОбычнаяФорма_XML.epf (8.3.5 and above) and ВыгрузкаСтруктурыКонфигурации_УправляемаяФорма_XML_JSON.epf (8.3.6 and above, the format is selected on the form).

Platform help — the shcntx_ru.hbk file from the 1C installation directory:

/opt/1cv8/<версия>/shcntx_ru.hbk
C:\Program Files\1cv8\<версия>\bin\shcntx_ru.hbk

The name must match exactly. The same directory contains hundreds of .hbk files — 38 different help systems, each in two dozen languages. Ones similar to the needed file:

File

What it is

Why it does not fit

shcntx_root.hbk

the same help, language-independent part

25,508 items, but not a single description: only the page tree and English identifiers, without appearance versions

shlang_ru.hbk

built-in language description

not a 1C container at all

shquery_ru.hbk

query language

the same

config_ru.hbk

configurator help

a container, but no syntax assistant pages inside

1cv8_ru.hbk

user guide

not a container

They cannot be told apart by size: shcntx_root.hbk weighs 33 MB against 39 MB for the needed one. The _ru suffix is the language, _root is the common part without texts.

If the file is wrong, the server explains exactly why and leaves the previous help in place.

One help from the newest available platform is enough: each item carries an appearance version, and for old configurations the excess is filtered out. If the version is not in the path, it is derived from the data itself.

Help from old platforms is also accepted — it is marked up differently (sections on div instead of p), and that is accounted for. It is useful when standing up a separate server for old deployments: help from 8.3.5 gives 18,936 items and does not contain СтрНайти, СтрРазделить, ЗаписьJSON — which did not exist in 8.3.5. But such help does not report its version: there are no "starting with version" marks in it, because back then everything was current. Therefore the version is taken from the file or directory name — put it as 8.3.5.1570.hbk or in data/hbk/8.3.5.1570/, otherwise matching with the configuration will not work.


5. How it is structured

src/mcp1c/
  v8container.py     контейнер 1С — общий для .hbk, .cf, .epf
  syntax_parser.py   разбор справки платформы
  syntax_model.py    модель элемента справки, виды, границы версий
  syntax_merge.py    слияние справок разных версий в один индекс
  query_parser.py    разбор справки по языку запросов (shquery_ru.hbk)
  replacements.py    чем заменить функцию, которой нет в старой платформе
  virtual_tables.py  таблицы запроса регистров и имена их полей
  loader.py          чтение выгрузок, XML и JSON в одну модель
  model.py           модель конфигурации
  graph.py           граф связей
  graph_view.py      окрестность объекта для картинки на дашборде
  search.py          лексический поиск
  search_keys.py     формулировки, которыми спрашивают язык запросов
  synonyms.py        встроенный словарь: как говорят против того, как названо
  dictionary.py      локальный словарь поверх встроенного
  index_cache.py     кэш поисковых индексов, расходный
  modules_index.py   оглавление, вызовы и формы для get_callers
  store.py           чтение и запись разобранных справок
  render.py          markdown-карточки объектов и элементов
  intake.py          отбор и распаковка выгрузки в файлы, санитизация имён
  incoming.py        состояние data/incoming: кэш sha256, причина отказа
  registry.py        реестр источников, сопоставление версий
  tools.py           десять инструментов, без зависимости от MCP
  server.py          протокольный слой (единственная внешняя зависимость)
  dashboard.py       веб-интерфейс: реестр, запросы, словарь
  cli.py             отладочный CLI
  bench.py           стенд замеров качества поиска

One model for two formats. XML and JSON are different serializations of one schema; the loader reduces both to the same dictionary. Verified: both exports give the same set of 30 keys.

The graph is built by the loader, not by 1C. Edges are derived from attribute types, document movements, entry bases, owners, subscription handlers, and scheduled job methods. Rules can be changed without re-exporting.

Weak edges. Attributes like ЗначениеДоступа enumerate hundreds of types and connect almost everything to everything. Such links are marked weak and hidden by default — otherwise the useful ones drown in them.

Detail levels. A full description of Документ.ЧекККМ (50 attributes, 17 tabular sections) consumes the entire context. brief — a couple of lines, fields — the composition, full — with links.

No external databases. Production indexes stay in one process: compact code arrays respond without a separate network hop, and search takes 0.18–1.5 ms. Elasticsearch, a vector store, and a graph database do not pay off as a separate process and maintenance at this volume. Vector search would additionally require an encoder model (+185–620 MB to the image) and 16–32 ms just to encode the query.

Measured on real data — 2026-08-18

What is loaded on the working server:

Configuration

Platform

Objects

Edges

Accounting for Kazakhstan

8.3.27.1936

3,492

84,426

Document Management CORP

8.3.27.1936

4,596

50,554

Payroll and HR Management

8.3.27.1936

5,181

100,136

Retail for Kazakhstan

8.3.23.1997

5,637

58,345

Industry configuration

8.3.5.1570

1,616

29,288

Total

20,522

322,749

The set is random — it is what happened to be at hand. What matters is not the configuration itself but the platform spread: 8.3.5, 8.3.23, and 8.3.27 in one registry, and each configuration answers according to its own.

Plus the platform help — a merged index of three versions (8.3.5, 8.3.23, 8.3.27), 25,691 items, and the query language — 127 pages as a separate source.

Warm start of the final image with 12 module index cache files — 19 s on the working corpus measured on 2026-08-21. On a cold start, sources are parsed, indexes are built and placed into data/index/cache/, parsed help — into data/index/syntax/. After that they are loaded from there.

The cache is derived and expendable: tied to the Python version, the package code fingerprint, and the source hash. If anything does not match, indexes are rebuilt. The directory can be deleted at any time; it will restore itself. If the cache volume is read-only, the inability to delete a stale file does not interrupt removing a source from accounting.

The order of records in registry.json does not matter: code is restored only after its configuration. If its record is missing, was deleted, or replaced during startup, an old module or extension record is not published without an owner.

Search index postings live in numpy arrays; temporary dictionary population is freed immediately after freezing. The server's total RSS cannot be obtained by summing isolated layers: the cold peak and the warmed container state are captured separately after all sources are up.

Module texts — code tools connected

The acceptance of configuration export to files is implemented — section «Exporting configuration to files» above: code and forms land on disk, the source is accounted for in the registry, four internal indexes are built and cached. search_procedures searches exact names and exported procedures by words, get_procedure returns a module table of contents or a card with a body window, and get_callers combines confirmed call sites, metadata bindings, and form handlers without reading bodies.

The current production slice of the main configuration as of 2026-08-21: 137,116 procedures, 619,029 call sites, 3,194 forms, 89,528 form items, and 24,202 event binding lines.

Reproducible on its own export without printing its name or path:

MODULES_ROOT=/путь/к/выгрузке
.venv/bin/python tools/lab/measure_modules_cache.py "$MODULES_ROOT"

The meter prints JSON aggregates with keys procedures, calls, forms, elements, and event_rows. The last counter is raw <Event> rows before grouping and deduplication in public bindings.

The historical prototype snapshot was taken 2026-08-20 on an anonymized hierarchical export to files: 7,878 modules, 137,115 procedures, and 260 MB of text. The numbers below are exactly those, not the current aggregates.

Layer

On disk

In memory

table of contents of 137,115 procedures

14.9 MB

62 MB

619,030 resolved and unresolved call sites

12.0 MB

51 MB

search across 49,181 exported procedures

4.27 MB

173 MB

forms: 3,194 files, 69,769 elements

5.8 MB

44 MB

module texts and signatures

260 MB

0

Search latency is 1.2–1.5 ms. A full cold build of the four indexes with cache write takes about 50 s.

There are two different file exports, and the second was measured separately — an industry configuration 10.5.1.3 on 8.3.5: flat layout, modules in .txt, ordinary form code inside binary .Form containers. 2,603 modules, 33,555 procedures, parsing 1.1 s, search across all 94 MB at a median of 0.2 ms. This format does not contain form structures, and some common modules are shipped compiled — they have no source at all. Such modules are visible at ОбщийМодуль.<Имя>, but the procedures and calls inside them cannot be read. Therefore summaries, search, the table of contents of an ordinary module, reverse search, and overlap information warn above the result that the aggregates are given only for the available sources and may be incomplete.

The measurement scripts live in tools/lab/; they reproduce every figure above:

.venv/bin/python tools/lab/measure_modules_cache.py <каталог выгрузки в файлы>
.venv/bin/python tools/lab/measure_modules.py <каталог выгрузки в файлы>
.venv/bin/python tools/lab/measure_resident.py <каталог> <файл индекса> собрать
.venv/bin/python tools/lab/measure_search.py <файл индекса> экспортные
.venv/bin/python tools/lab/measure_forms.py <каталог>
.venv/bin/python tools/lab/measure_flat.py <каталог плоской выгрузки>
.venv/bin/python tools/lab/measure_container_memory.py --mode cold --data data \
  --timeout 300 --poll-interval 0.5
.venv/bin/python tools/lab/measure_container_memory.py --mode warm --data data \
  --timeout 300 --poll-interval 0.5

Search quality

Measured by a bench, reproduced with a single command:

PYTHONPATH=src .venv/bin/python -m mcp1c.bench \
    --data data --config РозницаДляКазахстана \
    --auto --sets query-language,roznica-metadata,modules-procedures \
    --check-notes

Set

Queries

P@1

P@3

P@5

P@10

MRR

Gap

Query language

21

85.7%

85.7%

90.5%

90.5%

0.869

35.0%

Retail metadata

21

81.0%

90.5%

90.5%

95.2%

0.862

93.2%

Module procedures

3

0%

0%

0%

33.3%

0.047619

0%

Exact help names

50,926

97.1%

98.3%

98.7%

98.9%

0.978

91.7%

Same-name

10,544

98.7%

99.8%

99.9%

100%

0.992

93.9%

The whole table was captured 2026-08-21 with the exact command above in read-only mode, without --save; the command exited with code 0. The three manual sets were assembled from live phrasings and misses, the last two are built from the data itself. Procedures have ranks [miss, miss, 7]. This is a baseline for improvements, not an acceptance threshold.

The procedures row is reproduced faster separately:

PYTHONPATH=src .venv/bin/python -m mcp1c.bench \
    --data data --config РозницаДляКазахстана \
    --sets modules-procedures

"Gap" — how far the first result is from the second, by median. It answers the question "confident hit or a fluke": 35% for the query language versus 91.7% for help means those wins hold three times weaker and a ranking tweak can flip them without moving a single percentage point of P@1.

Search latency is 0.18–1.4 ms per query depending on the set.

Query sets are not included in the image (tests/ in .dockerignore): measure from a working copy, not from the container.

Tests

.venv/bin/pip install -r requirements-dev.txt
.venv/bin/python -m pytest          # 1100 тестов (прогон 2026-08-21)

They do not depend on the contents of data/: there are no proprietary exports in the repository, everything needed is built synthetically in tests/conftest.py.

Search quality is not checked by tests — it is measured by the bench (mcp1c.bench, see "Measured"). Percentage thresholds would break on every dictionary edit, so the bench prints numbers and a human makes the decision. pytest checks observable behavior: "index was not rebuilt", "results matched", "startup did not crash".


6. Security

Two tokens, both set via environment variables. While a token is not set, the corresponding access is open to anyone who can reach the address.

Variable

What it protects

Not set

API_TOKEN

read: MCP tools and dashboard pages

configuration structure is open to all

ADMIN_TOKEN

write: source upload and deletion, incoming export parsing (/sources/incoming/parse), dictionary editing, /admin/reload

these routes are disabled, respond 404

The difference between "open" and "disabled" is intentional. Reading without a token works — on your own machine that is convenient and harmless. Writing without a token does not work at all: a single bad dictionary edit quietly breaks search for everyone connected to the shared server.

The token is passed via a header — either X-Api-Token or Authorization: Bearer <token>. The admin token also works for reading: otherwise the owner would have to keep two headers in the client instead of one.

The token must be in Latin characters. HTTP headers are encoded in latin-1, and a Cyrillic token physically never reaches the server: through the browser login form it will work, through a client header it will not.

Two paths are skipped past the check: /health (the container healthcheck uses it, and it returns nothing beyond read rights) and /login — otherwise the login form would be behind the very authorization it issues.

Two more rules, not about tokens:

  • The MCP endpoint returns the configuration structure in full. Set API_TOKEN for any deployment beyond your own machine; network access alone is not enough.

  • The data/ directory is entirely in .gitignore — both .hbk with exports and parsed indexes. The help index is the same 1C company content, just unpacked. It once got in and sat there for 20 commits; the history was rewritten with git filter-repo, and the rule was reformulated by directory rather than by extensions: the check should not be "is this .hbk?" but "is this in data/?".


7. Documents

File

About

CHANGELOG.md

what was done and what was learned about 1C

docs/schema-v1.md

export format contract

docs/data-sources.md

what we take from which source

docs/query-language-design.md

structure of the query language source

docs/dashboard-design.md

dashboard structure

docs/modules-intake-design.md

accepting a configuration export into files — spec

docs/modules-provider-design.md

code provider indexes and tools

exporter-1c/README.md

export processing for 1C

CONTRIBUTING.md

how to edit the project: language, commits, checks

SECURITY.md

how to report a vulnerability

Six self-contained contract documents are published: the export format, the source boundary, the structure of the query language and dashboard, code export intake, and the code provider. Every link from a public file must lead to material that is also in the repository.

External DB, vectors, graph DB, SSE transport, and lazy loading were rejected by measurements on the current corpus. They can only return with new measurements that refute the previous cost or show a new live scenario.


8. License

Apache License 2.0. It is also worth reading NOTICE separately — there are two things there that the license itself does not say:

  • the project was developed independently and is not affiliated with 1C LLC; "1C" and "1C:Enterprise" are trademarks of 1C LLC, and the license grants no rights to them (Apache 2.0, section 6);

  • the repository contains no platform help and no configuration exports — neither in source form nor parsed. These are 1C company content and data from specific deployments; everyone takes them from their own distribution and puts them in data/. The license covers the code, not the data you load into it.

Available Tools

11 tools
compare_configurationsA

Сравнить один и тот же объект в двух конфигурациях: чем различается состав реквизитов.

ParametersJSON Schema
NameRequiredDescriptionDefault
configsNoИмена конфигураций из `list_configurations`. Не заданы — берутся все загруженные.
full_nameYesПолное имя объекта, которое ищется в обеих конфигурациях.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full behavioral disclosure burden. It does communicate the comparison outcome, but it does not explicitly state read-only behavior, what happens if the object is missing from one configuration, or how missing configs are handled beyond the schema's default value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single short sentence that leads with the action and the comparison result. It contains no filler, redundancy, or repeated schema information, and every phrase contributes to understanding the tool's core behavior.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and full parameter documentation, the description is adequate for a fairly simple comparison tool. However, the complete lack of usage-routing guidance and behavioral caveats leaves some context missing, especially since no annotations exist to fill the gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so both full_name and configs are already documented with clear descriptions and the default behavior. The description adds only a loose mapping between 'two configurations' and the configs parameter, providing no extra semantic value beyond the schema; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action, 'Сравнить один и тот же объект в двух конфигурациях', and defines the exact output: 'чем различается состав реквизитов'. It clearly identifies the tool as a cross-configuration comparator, distinguishing it from siblings like get_object and search_objects.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies its use case, comparing an object across configurations, but it names no alternatives and gives no explicit when-to-use vs. when-not-to-use guidance. The schema mentions list_configurations for the configs parameter, but the description itself does little to route the agent away from sibling tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_callersA

Кто вызывает точную процедуру: подтверждённые места в коде с процедурой-владельцем, привязки подписок и регламентных заданий из метаданных, элементы и события формы. Одноимённые вызовы без разрешённого модуля показываются отдельно и не приписываются запрошенному адресу. Тела модулей не читаются.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoСколько мест вызова показать: целое число от 1 до 50. Оставшееся число подтверждённых мест и модулей указывается отдельно; одноимённые места без разрешённой цели тоже не выводятся без границы.
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.
addressYesТочный адрес процедуры `Модуль::Имя`, полученный из `search_procedures` или оглавления `get_procedure`.
extensionNoИмя одного загруженного расширения. Не задано — места в коде ищутся только в основной конфигурации; чужой код не подмешивается.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden, and it delivers. It discloses that only confirmed locations are returned, that unresolved same-name calls are shown separately, and that module bodies are not read. These are meaningful behavioral details beyond a generic 'returns callers' statement.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact: three dense sentences that front-load the main purpose, then add scope caveats and a limitation. Every sentence earns its place, with no filler or repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema exists and all parameters have rich schema descriptions, the definition is complete. The tool description covers scope, edge-case handling for unresolved same-name calls, and the fact that module bodies are not read, which is enough for an agent to use it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the parameters are already well documented in the schema. The tool description does not add extra parameter semantics, but it does not need to; the schema already explains address, limit, config, and extension. A baseline of 3 is appropriate here.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Кто вызывает точную процедуру' — who calls the exact procedure. It then enumerates exact kinds of call sites (code locations, subscription/scheduled-job bindings, form items/events), making it clearly distinct from siblings like search_procedures or get_procedure.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the intended use clear: given an exact procedure address, identify its confirmed callers. It also communicates an important scope rule by stating that unresolved same-name calls are not attributed to the requested address. It does not explicitly name alternatives or exclusion conditions, but the context is unambiguous enough for an agent to select it correctly.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_objectA

Структура объекта конфигурации: реквизиты с типами, табличные части, движения, предопределённые. detail: brief | fields | full. Обязательный шаг перед написанием кода или запроса. Только здесь видно то, от чего код зависит и чего нет в поиске: вид и периодичность регистра (СрезПоследних есть лишь у периодического регистра сведений, а непериодических большинство), корреспонденция, предел субконто, и — для регистров — раздел «Таблицы запроса» с уже подставленными именами полей: ресурс Количество в запросе называется КоличествоОстаток или КоличествоОборот, и в конфигураторе таких имён не видно.

ParametersJSON Schema
NameRequiredDescriptionDefault
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.
detailNoУровень детализации: `brief` — пара строк со счётчиками, `fields` — состав для написания кода, `full` — со свойствами и связями. Полное описание крупного документа занимает много контекста, поэтому `full` только когда связи действительно нужны.fields
full_nameYesПолное имя объекта: `Документ.ЧекККМ`, `Справочник.Номенклатура`. Получается из `search_objects`.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses return content and the impact of the detail parameter (e.g., 'full' can be large), which is helpful. However, it does not explicitly state that the operation is read-only or has no side effects, which would be expected for a getter without annotation cover.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a compact paragraph with several useful clauses. It front-loads the core purpose and packs relevant context (like the register periodicity note and query table names) without excessive verbosity. It's slightly long but every sentence adds information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given an output schema exists, return values are covered elsewhere. The description explains when to use the tool, the meaning of detail levels, and provides a crucial behavioral note about field name substitution in queries. This makes it largely complete for an agent to call correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and each parameter is described, giving a baseline of 3. The description adds value by explaining that 'full_name' comes from search_objects and by clarifying the semantics of the 'detail' parameter (brief/fields/full with usage guidance), going beyond the schema's bare descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves the structure of a configuration object, enumerating specific components (attributes with types, tabular sections, movements, predefined). It also positions it as a mandatory step before coding or querying, which makes the purpose unambiguous and distinct from broader search tools like search_objects.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly says this is a required step before writing code or queries, and contrasts it with search ('Только здесь видно то, от чего код зависит и чего нет в поиске'), which gives clear when-to-use context. It doesn't name specific sibling tools like get_related or get_procedure, but the usage intent is well defined.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_procedureA

Оглавление модуля либо карточка одной процедуры из загруженной выгрузки кода. Адрес модуля без :: возвращает только оглавление без тела; Модуль::Имя возвращает сигнатуру, контекст компиляции и окно тела до 200 строк с готовым вызовом продолжения. Выбранное расширение читается отдельно от основной конфигурации; смысл его аннотации показывается явно.

ParametersJSON Schema
NameRequiredDescriptionDefault
linesNoРазмер окна тела: целое число от 1 до 200. Если тело длиннее, ответ содержит готовый вызов следующего окна без пропусков.
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.
addressYesТочный адрес модуля (`ОбщийМодуль.ОбщегоНазначения`) или процедуры (`ОбщийМодуль.ОбщегоНазначения::Проверить`).
extensionNoИмя одного загруженного расширения. Не задано — читается код основной конфигурации; чужие расширения в тело не подмешиваются.
start_lineNoНомер первой строки окна тела, начиная с 0. Значение берётся из готового вызова продолжения в предыдущем ответе.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden and does meaningful work: it discloses that a bare address returns no body, that the body window is capped at 200 lines, that a ready continuation call is included, and that a selected extension is read separately from the main configuration. It does not cover error/not-found behavior, but the core execution traits are clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, each earning its place: the general purpose, the two address-mode behaviors, and the extension-isolation behavior. Key information is front-loaded and there is no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter tool with no annotations and an output schema, the description is complete enough: it covers address semantics, body windowing, continuation pagination, and extension handling. Config disambiguation is already documented in the schema, and the output schema covers return structure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value beyond the schema for `address` by explaining the `::` distinction that controls whether the body is returned, and for `extension` by noting it is read separately with explicit annotation meaning. The remaining parameters are already well documented in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific resource and distinct behaviors: it returns either a module's table of contents or a single procedure card from the loaded code export. It also explains the two address modes (`Module` vs `Module::Name`) and lists included elements (signature, compilation context, up-to-200-line body window), so an agent can distinguish it from sibling tools like search_procedures or get_syntax.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear usage context: use a bare module address for a table of contents without the body, and use `Модуль::Имя` for a full procedure card with body window and continuation call. It does not explicitly name alternative tools or exclusion conditions, but the intended invocation patterns are unambiguous.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_syntaxA

Полное описание элемента платформы 1С (сигнатура, параметры, тип возврата, доступность, версия появления, пример). Вызывать перед использованием функции на старой конфигурации: недоступное в её версии помечается, и там же лежит рецепт замены, если он записан (СтрРазделить появилась в 8.3.6 — на 8.3.5 нужен обход). Поле «Доступность» — тоже ошибка компиляции: серверный метод из клиентского контекста не соберётся.

ParametersJSON Schema
NameRequiredDescriptionDefault
nameYesИмя элемента платформы: `СтрНайти`, `ЗаписьJSON.ЗаписатьНачалоОбъекта`, `ValueTable.Columns`. Для членов объектов надёжнее указывать `Объект.Член`.
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.
detailNoУровень детализации: `brief` — пара строк со счётчиками, `fields` — состав для написания кода, `full` — со свойствами и связями. Полное описание крупного документа занимает много контекста, поэтому `full` только когда связи действительно нужны.fields

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full behavioral burden. It discloses non-obvious behavior: unavailable version constructs are flagged, a replacement recipe may be present, and the 'Availability' field corresponds to a compile error, e.g., a server method used from a client context. This is substantial and genuinely useful behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two dense sentences that front-load the result shape, then provide usage guidance, a concrete example, and a compile-error nuance. Every sentence earns its place and there is no filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Between the detailed schema, the output schema, and the description's explanation of when and why to call this tool, an agent has everything needed to invoke it correctly. The old-configuration workflow, the replacement-recipe behavior, and the availability-as-compile-error nuance are all covered.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The input schema already documents the name, config, and detail parameters thoroughly, including nested-object member syntax and config disambiguation. The tool description adds no parameter-specific meaning beyond that.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool's purpose: return the full description of a 1C platform element, including signature, parameters, return type, availability, version, and an example. It is specific and informative, but it does not explicitly name or contrast a sibling like search_syntax, so sibling differentiation is implicit rather than direct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives a clear usage rule: call this tool before using a function on an old configuration, because version-unavailable constructs are marked and a replacement recipe may be provided. It does not spell out when not to use the tool or name alternative tools, so it stops short of full when/when-not coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_configurationsA

Какие конфигурации 1С загружены, на какой платформе и что по ним доступно. Вызывать первым: если конфигураций больше одной, параметр config обязателен во всех остальных инструментах, а имя для него берётся отсюда.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

There are no annotations, so the description carries the behavioral burden. It discloses that the tool is a read-oriented listing, reveals the returned concepts (configurations, platform, availability), and explains an important behavioral consequence: the config name should be sourced from this tool. It does not explicitly state read-only or side-effect-free behavior, but the query-like phrasing makes the intent clear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences with no filler. The core purpose is front-loaded, followed immediately by the most important usage rule. Every clause earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter tool with an output schema present, the description provides the essential context an agent needs: what the tool lists, why it exists, and how its output feeds into other tools. Nothing critical is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the baseline is 4. The description correctly mentions `config` as a parameter for other tools, not this one, and adds no unnecessary parameter detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states what the tool reports: which 1C configurations are loaded, on which platform, and what is available for them. It also distinguishes itself from siblings by framing itself as the entry point that supplies the `config` value other tools need.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit usage guidance: call this tool first, and if more than one configuration exists, all other tools require the `config` parameter whose value comes from this result. This directly tells an agent when and why to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_extensionsA

Показать расширения, фактически действовавшие в снятом сеансе 1С, не применённые расширения и порядок элементов в ответе API платформы. Вызывать после list_configurations, когда задача зависит от активности расширений. Без отдельного runtime-снимка возвращает unknown; позиция API не выдаётся за доказанный порядок исполнения модулей.

ParametersJSON Schema
NameRequiredDescriptionDefault
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that without a separate runtime snapshot it returns 'unknown', and that API position order is not presented as proven module execution order. These caveats are valuable and go beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded with purpose, followed by usage and caveats. Every sentence serves a distinct informative role, with no filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema exists, the description does not need to explain return values. It covers purpose, usage context, prerequisite, and important behavioral caveats. It adequately equips an agent to determine when and how to call the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100% and the `config` parameter is already well-described (name as returned by `list_configurations`, mandatory when multiple configurations). The tool's description adds no additional parameter detail beyond what the schema provides, so a baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: showing extensions that were actually active in a captured 1C session, unapplied extensions, and the order of elements in the platform API response. It references the prerequisite `list_configurations` and differentiates itself by focusing on extension activity within a specific session context.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides explicit guidance to call after `list_configurations` when the task depends on extension activity. Gives a clear context and prerequisite, though it doesn't list when-not to use alternatives. No direct sibling for extensions exists, so the contextual direction is adequate.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_objectsA

Найти объект конфигурации по описанию или части имени. Запрос можно писать по-человечески: «расходная накладная», «цены номенклатуры». Отдаёт только имена и счётчики — состава полей здесь нет. Прежде чем писать код или запрос по найденному объекту, вызовите get_object: вид и периодичность регистра, готовые имена полей виртуальных таблиц (КоличествоОстаток, СубконтоДт1) приходят только оттуда. Запрос, написанный сразу после поиска, выглядит правильным и падает на «поле не найдено».

ParametersJSON Schema
NameRequiredDescriptionDefault
kindNoОграничить вид: Документ, Справочник, РегистрСведений, РегистрНакопления, Перечисление, ОбщийМодуль и т. п.
limitNoСколько результатов вернуть. По умолчанию 10, максимум 50 (большее молча урезается). Поднимать выше 10 стоит только когда нужного не оказалось в первой десятке: правильный ответ почти всегда в первой пятёрке, а длинная выдача тратит контекст.
queryYesФормулировка по-человечески («расходная накладная») или часть имени объекта.
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of behavioral disclosure. It clearly states the limited output shape (names and counters only) and the failure mode when field information is missing. It does not mention auth or rate limits, but for a search tool the output limitation is the most important behavioral trait.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four sentences, each earning its place: purpose, natural-language input, output scope, and the required follow-up with failure warning. There is no filler, and the most actionable guidance is clear and memorable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a search tool with a complete schema and an output schema, the description covers purpose, output limitations, and the follow-up needed to write correct code. It could mention alternatives like search_procedures, but the tool relationships are otherwise well handled by naming get_object explicitly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents query, kind, limit, and config. The description adds examples and a limit heuristic but no genuinely new parameter semantics; baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a concrete verb and resource: find configuration objects by natural-language description or partial name, and explicitly notes it returns only names and counters. This distinguishes it from get_object, which must be called afterward for field metadata.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Directly instructs the agent to call get_object after searching before writing code or queries, and warns that queries written immediately after search will fail with 'поле не найдено'. Also gives actionable guidance on keeping limit at 10 unless the needed result is absent from the first ten.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_proceduresA

Найти процедуру или функцию в загруженном коде конфигурации либо одного выбранного расширения. Точное имя находит и неэкспортные процедуры; поиск по словам — только экспортные. Расширенная фраза о 12 типовых событиях разрешается в точное имя, но без scope не выбирает случайную реализацию. Для обычного поиска scope задаёт приоритет модулей, для распознанного события — ограничивает его реализации. Из query область не угадывается. Сигнатуры читаются из выгрузки в файлы только для показанных результатов.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoСколько результатов вернуть отдельно на каждом уровне поиска: по точному имени и по словам. Целое число от 1 до 50; значение вне диапазона отклоняется, чтобы не скрывать ошибку вызова.
queryYesТочное имя процедуры (`ПриЗаписи`) или слова из имени и комментария-шапки (`проверить остатки`), в том числе фраза о поддержанном типовом событии (`что выполняется при записи объекта`). Объект поиска из этого текста не угадывается — для него есть `scope`.
scopeNoНеобязательный явный scope: объект (`Документ.ЧекККМ`) или точный адрес (`ОбщийМодуль.ОбщегоНазначения`). В обычном поиске его модули поднимаются, а распознанное типовое событие разрешается только внутри scope. Из query область не выводится.
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.
extensionNoИмя одного загруженного расширения. Не задано — поиск идёт только по коду основной конфигурации, без примеси расширений.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.4/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does so thoroughly. It discloses that event phrases resolve without picking an arbitrary implementation, that scope changes priority or constraint semantics, that query does not imply scope, and that signatures are read from file dumps only for displayed results. These are non-obvious behavioral details beyond the schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact, front-loaded with the core purpose, and each sentence adds meaningful detail. However, it is somewhat dense and partially repeats scope/query semantics already documented in the input schema, so it is not maximally tight.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, the presence of an output schema, and full parameter coverage in the schema, the description covers all necessary operational aspects: search modes, event recognition behavior, scope semantics, config/extension context, and result-dependent signature reading. An agent has enough information to select and invoke the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The narrative description mostly restates scope semantics already present in the input schema and adds no new parameter-level detail beyond it. The schema itself is doing the heavy lifting.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a clear verb and resource: 'Найти процедуру или функцию' in loaded configuration code or one selected extension. It further differentiates exact-name search from word-based search and notes exported versus non-exported coverage, making the tool's purpose immediately recognizable among siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides practical usage context: exact names find non-exported procedures, word search finds only exported ones, scope behaves differently for ordinary search versus recognized events, and config is tied to the loaded configuration. It does not explicitly name sibling alternatives or state when not to use this tool, so it stops short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_syntaxA

Найти метод, свойство или объект платформы 1С. Даёт только строку списка. Сигнатура, параметры, доступность по контекстам, версия появления и рецепт замены для старой платформы — в get_syntax по найденному имени.

ParametersJSON Schema
NameRequiredDescriptionDefault
kindNoОграничить вид: method, property, event, object, query_table, query_field.
limitNoСколько результатов вернуть. По умолчанию 10, максимум 50 (большее молча урезается). Поднимать выше 10 стоит только когда нужного не оказалось в первой десятке: правильный ответ почти всегда в первой пятёрке, а длинная выдача тратит контекст.
queryYesЧто ищем: «разделить строку», «ЗаписьJSON», «StrFind» по платформе. Русские и английские имена равнозначны.
configNoИмя конфигурации 1С, как его вернул `list_configurations` (например «ОтраслеваяКонфигурация»). Обязателен, если загружено больше одной конфигурации: по умолчанию ничего не подставляется, иначе ответ может относиться к чужой конфигурации.

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the behavioral disclosure burden. It clearly states the key constraint: 'Даёт только строку списка' (gives only a list line), and points to get_syntax for the full syntax. It does not mention read-only status or matching semantics, but for a search tool the limited output is the main behavioral trait and it is covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences front-load the purpose and immediately disclose the output limitation and the alternative for richer results. Every sentence earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple search purpose, a fully documented schema, and an output schema, the description is largely complete for calling the tool correctly. A minor gap is the absence of explicit guidance about when not to use it versus config-oriented search tools, but the 'платформы 1С' qualifier mostly resolves this.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, and the schema already documents query, kind, limit, and config with helpful guidance. The tool description adds no parameter-specific detail beyond the schema, so the baseline of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb with a clear resource: 'Найти метод, свойство или объект платформы 1С' (find a method, property, or object of the 1C platform). It also distinguishes itself from get_syntax by stating that signature, parameters, context availability, version, and replacement recipe live in the sibling tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives an explicit routing rule: for detailed syntax information, use `get_syntax` on the found name. It does not explicitly list exclusions for other search siblings like search_objects or search_procedures, but the phrase 'платформы 1С' narrows the scope to platform entities rather than configuration objects.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 11 tool updatesv2.0.0
    • First observedcompare_configurations
    • First observedget_callers
    • First observedget_object
    • First observedget_procedure
    • First observedget_related
    • First observedget_syntax
    • First observedlist_configurations
    • First observedlist_extensions
    • First observedsearch_objects
    • First observedsearch_procedures
    • First observedsearch_syntax

TDQS

A4.4/5.0
Disambiguation5/5

Each tool targets a distinct resource and action: configurations, extensions, metadata objects, procedures, callers, object structure, relationships, comparison, and platform syntax. The search/get pairs are complementary rather than overlapping, with clear descriptions preventing misselection.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern using list_, search_, get_, and compare_. Plural nouns for list/search tools and singular/getter forms are applied predictably across the set.

Tool Count5/5

Eleven tools is well-scoped for a 1C analysis server. Each tool covers a necessary step in the workflow from discovering configurations to inspecting objects, procedures, relationships, and platform syntax, with no redundant entries.

Completeness5/5

The tool surface covers the full read-only analysis lifecycle: configuration and extension discovery, object search and structure, procedure search and details, callers, relationships, configuration comparison, and platform syntax lookup. No obvious dead ends or critical missing operations are apparent for the stated purpose.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    F
    maintenance
    Provides a RAG-based search system for 1C:Enterprise platform documentation using hybrid BM25 and semantic search across multiple versions. It enables developers to retrieve API signatures, methods, and usage examples directly within IDEs or through a REST API.
    22
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server for searching and analyzing 1C enterprise metadata and BSL code using a SQLite backend. Enables querying configuration structure, code routines, and performing compliance checks via natural language.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server for RAG-based search over 1C Enterprise configuration documentation, enabling natural language queries to find objects like справочники, документы, and отчеты.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/AzeevAN/mcp-1c'

If you have feedback or need assistance with the MCP directory API, please join our Discord server