Skip to main content
Glama
skiddgoddamn

1c-mcp

by skiddgoddamn
README.md
<div align="center">

# 1c-mcp

**Give an AI agent eyes into your [1C:Enterprise](https://1c.ru) 8.3 base — read metadata & data, run queries, and diagnose why a document won't post — over one extension HTTP service.**

![license](https://img.shields.io/badge/license-MIT-blue)
![MCP](https://img.shields.io/badge/MCP-server-6E56CF)
![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178C6?logo=typescript&logoColor=white)
![1C](https://img.shields.io/badge/1C-Enterprise%208.3-FFD200)
![PRs welcome](https://img.shields.io/badge/PRs-welcome-34c759)

</div>

## Why

Most 1C automation tooling either reads metadata **or** reads data. The thing an analyst actually gets stuck on — *"this document won't post, why?"* — needs both, plus the ability to run the posting logic and capture the error. `1c-mcp` does exactly that.

It exposes your base as [MCP](https://modelcontextprotocol.io) tools through a thin HTTP service in a **configuration extension**. The MCP server stays a small, strict TypeScript client; the reasoning about *how to fix* a broken document is left to the agent (Claude) — no LLM baked into the server, no API keys to manage.

- 🔎 **Metadata introspection** — objects, attributes, tabular sections, register records
- 📄 **Data & queries** — read objects by ref, run 1C query-language, list documents, read register movements
- 🩺 **Document diagnosis** — test-post a document *inside a rolled-back transaction*, capture the fill errors, the exception text, the failing attribute, and the movements it tried to make — the whole context an agent needs to explain and fix it
- 🔒 **Read-only by default** — write tools (`object_update`, `document_post`) exist but only register when `ONEC_ALLOW_WRITE=1`
- 🪶 **TypeScript, ESM, MIT** — 2 runtime deps (`@modelcontextprotocol/sdk`, `zod`), no 1C secrets in the repo

## How document diagnosis works

`document_diagnose(ref)` is the headline. The extension runs, inside a transaction it always **rolls back**:

1. `ПроверитьЗаполнение()` — collects required-field / validation problems
2. `Записать(…Проведение)` in a `Попытка/Исключение` — captures the posting exception and every `Сообщить()`
3. reads back the register movements it *would* have made

…then returns one structured object. The agent gets the error text, the exact attribute/row that failed, the document data, the object's metadata, and the attempted movements — and works out the fix:

```
document_diagnose   # ref → { filled, fillErrors, posts, errorText, messages, wouldMove, data, metadata }
                    # agent reads it, explains the cause, proposes the change
object_update       # (if ONEC_ALLOW_WRITE=1) apply the fix
document_post       # (if ONEC_ALLOW_WRITE=1) post for real
```

Because the transaction is always rolled back, diagnosis has **zero side effects** on the base.

## Requirements

1. **1C:Enterprise 8.3** base published on a web server with HTTP services enabled.
2. A **configuration extension** exposing the HTTP service `mcp` (see [The 1C side](#the-1c-side) — this is the part you build, against a fixed contract).
3. A 1C user for the MCP to authenticate as (Basic Auth).
4. Node ≥ 18.

## Setup

```bash
npm install
cp .env.example .env      # set ONEC_URL / ONEC_USER / ONEC_PASSWORD
npm run build
```

Register it with your MCP client:

```json
{
  "mcpServers": {
    "1c": {
      "command": "node",
      "args": ["D:/projects/1c-mcp/dist/index.js"],
      "env": {
        "ONEC_URL": "http://localhost/mybase/hs/mcp",
        "ONEC_USER": "mcp",
        "ONEC_PASSWORD": "secret",
        "ONEC_ALLOW_WRITE": "0"
      }
    }
  }
}
```

| Env | Meaning |
|-----|---------|
| `ONEC_URL` | HTTP-service root: `http://<host>/<base>/hs/<rootURL>` |
| `ONEC_USER` / `ONEC_PASSWORD` | 1C user for Basic Auth |
| `ONEC_ALLOW_WRITE` | `1` registers the write tools; anything else = read-only |

> The env prefix is `ONEC_` (not `1C_`) on purpose — a var name starting with a digit breaks in POSIX shells.

## Tools

| Tool | Purpose |
|------|---------|
| `metadata_list` | List metadata objects (Catalogs / Documents / Registers …) with a filter. |
| `metadata_get` | Full structure of one object: attributes, tabular sections, register records. |
| `metadata_search` | Search objects by name/synonym across all kinds. |
| `object_get` | Read an object by GUID ref — attributes + tabular sections. |
| `query_run` | Run a 1C query-language query and return rows. |
| `document_list` | List documents by type / period / filter. |
| `register_records` | Register movements / balances by document or filter. |
| `document_diagnose` | Test-post in a rolled-back transaction → fill errors, exception text, failing attribute, movements, data, metadata. |
| `document_check_fill` | Fill check only (no test-post). |
| `object_update` ⚑ | Write an object's attributes / tabular sections. |
| `document_post` ⚑ | Post a document for real. |

⚑ — registered only when `ONEC_ALLOW_WRITE=1`.

## The 1C side

The MCP talks to one HTTP service. **You implement it** in a configuration extension, against this contract.

Root: `http://<host>/<base>/hs/mcp`. Every method: `POST`, JSON body, Basic Auth. Errors: non-2xx with body `{ "error": { "code": string, "message": string } }` (the client turns this into a tool error).

| Method | Request | Response (key fields) |
|--------|---------|-----------------------|
| `/metadata/list` | `{ kind?, filter? }` | `{ objects: [{ fullName, name, synonym, kind }] }` |
| `/metadata/get` | `{ fullName }` | `{ fullName, synonym, attributes[], tabularSections[], registerRecords[] }` |
| `/query/run` | `{ text, params?, limit? }` | `{ columns[], rows[][] }` |
| `/object/get` | `{ ref }` | `{ ref, presentation, fields{}, tabularSections{} }` |
| `/document/list` | `{ type, period?, filter?, limit? }` | `{ documents: [{ ref, number, date, presentation, posted }] }` |
| `/register/records` | `{ register, filter?, ref? }` | `{ records[] }` |
| `/document/diagnose` | `{ ref }` | see below |
| `/document/checkfill` | `{ ref }` | `{ filled, fillErrors[] }` |
| `/object/update` ⚑ | `{ ref, fields{}, tabularSections? }` | `{ ok, ref }` |
| `/document/post` ⚑ | `{ ref }` | `{ ok, posts, errorText? }` |

### `/document/diagnose` response

```json
{
  "ref": "guid",
  "presentation": "Реализация 0001 от 01.01.2026",
  "filled": false,
  "fillErrors": [{ "field": "Организация", "message": "Поле не заполнено" }],
  "posts": false,
  "errorText": "Недостаточно 5 шт номенклатуры X на складе Y",
  "messages": [{ "text": "...", "field": "Товары", "dataPath": "Товары[2].Количество" }],
  "wouldMove": [{ "register": "ТоварыНаСкладах", "records": [{ "...": "..." }] }],
  "data": { "Организация": null, "Товары": [{ "...": "..." }] },
  "metadata": { "attributes": [], "tabularSections": [], "registerRecords": [] }
}
```

### Extension setup checklist

1. Create a **configuration extension**, add an **HTTP service** with root URL `mcp`.
2. Implement the URL templates from the contract (method POST, JSON in/out).
3. The key handler `/document/diagnose` — a side-effect-free test-post:

   ```bsl
   Объект = Ссылка.ПолучитьОбъект();
   Результат = Новый Структура("filled, fillErrors, posts, errorText, messages, wouldMove, data, metadata");

   НачатьТранзакцию();
   Попытка
       Результат.filled = Объект.ПроверитьЗаполнение();     // messages collected below
       Попытка
           Объект.Записать(РежимЗаписиДокумента.Проведение);
           Результат.posts = Истина;
       Исключение
           Результат.posts = Ложь;
           Результат.errorText = ОписаниеОшибки();
       КонецПопытки;
       Результат.messages = ПолучитьСообщенияПользователю(Истина);
   Исключение
       Результат.errorText = ОписаниеОшибки();
   КонецПопытки;
   ОтменитьТранзакцию();   // always roll back — the base is never changed
   ```

   Fill `data` / `metadata` / `wouldMove` and return the JSON above.
4. Publish the base on a web server, enable HTTP services, create the `ONEC_USER` user.
5. Smoke test: `curl -u user:pass -X POST http://host/base/hs/mcp/metadata/list -d "{}"`.

## Development

```bash
npm test          # vitest — config, HTTP client, write-tool gating (no live 1C needed)
npm run build     # tsc → dist/
npx tsc --noEmit  # typecheck
```

The design spec and implementation plan live in `docs/superpowers/`.

## Security

- `document_diagnose` always runs inside a rolled-back transaction — zero side effects.
- Write tools register only when `ONEC_ALLOW_WRITE=1`.
- 1C credentials come from the environment only; `.env` is git-ignored and never committed.

## Contributing

PRs welcome. Good first ideas:

- a bundled reference extension (`.xml` sources of the HTTP-service module)
- OData transport as a read-only fallback (no extension required)
- richer `wouldMove` shaping / typed diagnose result
- Russian README

1. Fork and branch: `git checkout -b feature/my-change`
2. `npm install`; `npm run build` and `npx tsc --noEmit` must pass; `npm test` green
3. **Never commit secrets** (`.env`, passwords) or real base data
4. Open a PR describing what and why

## License

MIT