Servers and plugins
The LSP server for editors, the MCP server for agents, the local web panel, and plugins that add rules, data and severities of their own.
One engine, several ways to reach it: a long-living server for an editor, a tool surface for an agent, a page in a browser – and a way to extend all of them at once.
LSP server
xbsl-lsp (the [lsp] extra: pip install "xbsl[lsp]") runs the linter as a
long-living Language Server over stdio: live per-file diagnostics as you type, project-wide
diagnostics on save, go to definition, completion and hover over a resident project index,
and quick-fix code actions – without paying the interpreter start-up cost per call. Flags:
--project-root (the sources root relative to the workspace folder), --select/--ignore/
--enable, --data-dir, --baseline, --templates. Any LSP-capable editor (VS Code, Neovim,
JetBrains) can spawn it.
Everything an editor needs for code is standard LSP, so a plain client works with no extra
wiring. On top of that the server answers private xbsl/* requests – this is what the VS Code
panels are built on, and what another editor would use to reproduce them:
| Group | Requests |
|---|---|
| Diagnostics and hints | xbsl/relint, xbsl/hoverDoc, xbsl/templatesReload |
| Platform documentation | xbsl/docsAvailable, xbsl/docsSearch, xbsl/docsPage, xbsl/docsTree, xbsl/docsAsset, xbsl/docsForSymbol, xbsl/docsByName |
| Schemas and vocabularies | xbsl/uiSchema, xbsl/metadataSchema, xbsl/formKeys, xbsl/metaKeys, xbsl/metaCapabilities, xbsl/httpMethods |
| Metadata scaffolding | xbsl/objectInfo, xbsl/metaNewObject, xbsl/metaAddField, xbsl/metaSetFieldProperty, xbsl/metaAddForm, xbsl/metaAddRoute, xbsl/metaAddSubsystem, xbsl/metaAddLocalization, xbsl/localizationInfo |
| Forms | xbsl/formTree, xbsl/formNodeAt, xbsl/formEdit, xbsl/searchForms, xbsl/bindingComplete |
| Event handlers | xbsl/moduleHandlers, xbsl/addHandler, xbsl/addModuleMethod, xbsl/removeHandler |
A scaffolding request returns a plan – the full text of every file it would write – and the
editor applies it as one undoable edit; the server writes nothing itself (the CLI and the MCP
server, on the same code, do write). xbsl/metaCapabilities answers with the server version and
the kinds it can create – of objects, of section items, of forms – so a client can build its
menus from the running engine instead of hardcoding them.
MCP server
A thin adapter over the same core: an agent (e.g. Claude Code) can call the checks as tools and receive structured diagnostics.
pip install -e ".[mcp]"
claude mcp add xbsl -- xbsl-mcp
Every writing meta_* tool applies the changes and returns the lint of the written files in the
same response – creation and validation in one round trip. The core and the CLI do not require
mcp – it lives only in the [mcp] extra.
Every meta_* tool and lint_paths take root – the caller’s project root (an agent working
in a git worktree does not share the server’s working directory): relative directory,
yaml_path, module_path, paths and baseline resolve against it, and the answer carries
absolute paths (of the written files, of the diagnostics) plus the root they were resolved
from. Without it the server’s own working directory is used, as before – and a relative path
from a worktree then names the other checkout, whose clean answer looks like yours.
Checking and the environment
| Tool | What it does |
|---|---|
lint_paths(paths, select, ignore, enable, baseline, no_baseline, root) |
check files and directories on disk (relative paths – against root, the summary names it); the project’s .xbsllint-baseline applies on its own, exactly as in the CLI (summary.baselined counts what it suppressed, no_baseline reports the frozen findings too); enable adds a rule that is off by default on top of the defaults, the way a project asks for its translation gaps |
lint_source(filename, content, select, ignore) |
check in-memory content, before the file is written |
list_rules() |
the rules available here: id, title, tier, scope, severity |
version_info() |
the environment answering: engine, interpreter, data version, plugins – tells apart two environments that answer differently on the same file |
Platform reference and schemas
| Tool | What it does |
|---|---|
docs_search(query, limit) |
full-text search over the 1C:Element documentation |
docs_page(id, brief, section) |
a documentation page by the id returned by the two other tools; brief – the head alone: a summary and the section names instead of the text, section – the head plus one section of the article (Properties, Methods, Constructors, …; an unknown name answers with the names to choose from) |
docs_symbol(name, brief, section) |
the page for a symbol by name (a type or a member), with the same brief and section modes |
type_members(name) |
the members of a stdlib type in one compact answer – what can follow the dot; cheaper than a page when only the member list matters |
ui_schema(component, brief, property) |
the ui schema of an interface component: the designer’s palette and its typed properties |
metadata_schema(kind, sections, names) |
the properties an element of a given ElementKind may declare |
The three docs_* tools need the docs.sqlite database (see Documentation search); the two schema tools read the generated language data. A type page runs to thousands of characters – the constructors, every property, the inherited lists – so the whole article is for reading it: brief answers “which page is it and what is it about”, section answers one question about it.
Translating the sources (see Translating a project)
| Tool | What it does |
|---|---|
translate_status(root) |
the coverage and what is left - the cheap check before deciding anything; a root without a dictionary is refused, the answer naming where one is looked for |
translate_gaps(root, kind, filter, limit, offset, compact) |
what the dictionary does not cover yet, by page: the count, the first places, the platform’s own spelling as a hint; compact keeps only the key, the kind and the count per row; the answer names the dictionary it read |
translate_entries(root, kind, filter, limit, offset) |
what the dictionary already says, with the file and line of each entry |
translate_set(root, edits, edits_file, target, comment) |
write entries back: add, correct in place, or remove by emptying a value; edits_file is a batch file (the dictionary’s own yaml format or the JSON list), comment is the head line a newly created file gets |
The four answer in PAGES over one engine core, so filling a dictionary of thousands of entries never means reading the files.
The project and its objects
| Tool | What it does |
|---|---|
meta_project_info(root) |
map the sources under a root: projects, subsystems, objects by kind |
meta_object_info(root, name, yaml_path) |
describe one object: everything needed to write its forms and code |
meta_new_project(...) |
scaffold a project: Проект.yaml, Проект.xbsl and the first subsystem |
meta_new_object(directory, kind, name, ...) |
create an object: <Name>.yaml plus <Name>.xbsl for kinds with a module |
meta_rename_object(..., dry_run) |
rename an object and update every reference across the sources |
meta_delete_object(..., dry_run) |
delete an object whole: the yaml/module pair and its forms |
meta_add_subsystem(parent_dir, name, ...) |
create a subsystem folder with its Подсистема.yaml |
meta_add_dependency(root, vendor, name, version, ...) |
attach a library – the Libraries section of Проект.yaml |
meta_set_access(root, ..., default, permissions, calc_by) |
set AccessControl.Permissions on an object |
Fields, routes, methods, forms, localization
| Tool | What it does |
|---|---|
meta_add_field(yaml_path, field_kind, name, type, props, ...) |
add a section item: attribute, dimension, resource, enumeration value, parameter, field, tabular section. A built-in attribute (Number, Date, Code, Name, Owner) is judged by its own class, so Length, Uniqueness and the Autonumbering block are accepted and type may be omitted where the class fixes it; props takes a nested block as a dict or a dotted key (Autonumbering.Prefix) and a list as a sequence; a block of a class the metamodel does not describe (Presentation) is refused as a known limitation |
meta_set_field_property(yaml_path, field_kind, name, props, ...) |
set properties on a section item that already exists; the same value shapes as meta_add_field, a nested block replaces the old one whole |
meta_add_route(yaml_path, routes, template, methods) |
add url templates to an HttpService plus the handler stubs |
meta_add_method(module_path, name, params, returns, ...) |
insert a method into an .xbsl module without tearing annotation blocks apart |
meta_add_form(root, ..., forms, card_min_width, card_placeholder) |
generate forms for an object and register them in its Interface |
meta_add_localization(yaml_path, language) |
add a translation file to a localized-strings element |
meta_localization_info(yaml_path) |
the localization picture: declared languages and what is still untranslated |
Form components – the designer, scripted
| Tool | What it does |
|---|---|
meta_component_tree(yaml_path, node_id, name, max_depth, properties) |
the node tree of an interface component; a big form can be taken in parts - a subtree (by node id or by its Name), a depth limit and without the property records |
meta_add_component(yaml_path, parent_id, slot, ...) |
insert a new component into a slot of the parent node |
meta_insert_fragment(yaml_path, parent_id, slot, fragment, ...) |
paste a ready yaml block of one component (a copied subtree) into a slot |
meta_move_component(yaml_path, node_id, new_parent_id, slot, ...) |
move a node into another (or the same) slot; the comments above it travel along |
meta_move_components(yaml_path, node_ids, ...) |
move several nodes in one operation, keeping their document order |
meta_remove_component(yaml_path, node_id) |
remove a node with its attached comments |
meta_remove_components(yaml_path, node_ids) |
remove several nodes in one operation |
meta_set_component_property(yaml_path, node_id, key, value, value_yaml) |
set, replace or remove a property of a node |
meta_add_handler(yaml_path, node_id, key, method, signature) |
bind an event property to a handler method of the paired module |
The same operations are available through the CLI (Commands) and, for an editor, through
the xbsl/meta* LSP requests.
Web interface
A local page: point it at a project folder and see the diagnostics. Standard library only (no
external dependencies), binds to 127.0.0.1 only.
xbsl-web # then open http://127.0.0.1:8771/
Per-tier rule toggles, a data-version selector, severity/text filters, dark/light theme; clicking
a diagnostic opens the file in VS Code (vscode://).
Extending: your own rules, data and severities
Three entry point groups let a separate package extend the linter without forking it. This exists
for teams whose rules or language data cannot be published: keep those in a private package that
depends on xbsl.
# pyproject.toml of your package
dependencies = ["xbsl>=0.16"]
[project.entry-points."xbsl.rules"]
myproject = "myproject.rules" # importing the module runs its @rule decorators
[project.entry-points."xbsl.data"]
myproject = "myproject:data_root" # a path, or a callable returning one
[project.entry-points."xbsl.severity"]
myproject = "myproject:severity_overrides" # {rule id: "error"|"warning"|"info"|"off"}
Packages that declared the groups under the pre-rename name (xbsllint.rules/xbsllint.data/
xbsllint.severity) keep working: the legacy groups are scanned after the new ones.
The severity dict (or a zero-argument callable returning one) raises or lowers the default level
of any rule – built-in or plugin – for every run in this installation: a project may treat, say,
style/abbreviation-case as a warning while the published default stays info. "off" removes a
rule from the default set (an explicit --select/--enable still turns it on, at its base level).
Install the package and the CLI, the MCP server and the web UI all pick everything up – no flags,
no config file. A failing entry point raises instead of warning: a linter that silently drops a
rule stays green in CI and guarantees nothing; an override naming an unknown rule id or level
raises for the same reason. XBSL_NO_PLUGINS=1 ignores every external package (built-in
rules, bundled data and default severities only).