Language Server Protocol (LSP) server for authoring pain001 ISO 20022 Customer Credit Transfer Initiation data files.
Project description
pain001-lsp
Language Server Protocol server for authoring pain001 ISO 20022 payment-data JSON files.
Contents
Getting started
- What is pain001-lsp? — the problem it solves
- Install — PyPI, virtualenv, Docker
- Quick start — wire it to your editor in 60 seconds
Library reference
- Features — six LSP features as you type
- Editor wiring — Neovim, VS Code, Helix, generic
- Using the helpers — call the diagnostic engine from Python
- The pain001 suite — core lib, MCP server, LSP server
Operational
- When not to use pain001-lsp — honest boundaries
- Development — gates, make targets
- Security — sandboxing posture
- Documentation — examples, guides
- Contributing — how to get changes in
- License — Apache-2.0
What is pain001-lsp?
A Language Server speaks the
Language Server Protocol (LSP) —
the editor-agnostic protocol that lets a single backend deliver
diagnostics, completion, hover, code actions, formatting, and document
symbols to any LSP client (VS Code, Neovim, Helix, Emacs, …).
pain001-lsp is that backend for payment-data JSON files: the
JSON arrays of flat payment records that drive ISO 20022 pain.001
message generation in the
pain001 suite.
Every feature is backed by the pain001 public API, so editor
behaviour stays in lockstep with the CLI, REST API, and MCP server.
| Concern | How pain001-lsp handles it |
|---|---|
| Schema validation | Each record validated against the message type's input JSON Schema |
| Identifier validation | debtor_account_IBAN, creditor_account_IBAN, *_BIC, etc. validated as IBAN/BIC |
| Completion | Every input field with its schema description + every supported message type |
| Hover | Schema descriptions for the field under the cursor |
| Code actions | One-click "add missing required fields" with type-appropriate placeholders |
| Formatting | Two-space, ISO 20022 Latin-clean JSON pretty-print; idempotent (no edits when already formatted) |
| Outline | One DocumentSymbol per top-level record (named by id, detail = payment_id) |
| Multi-record | Code actions target the record under the cursor, not just the first |
| Live re-config | workspace/didChangeConfiguration switches the active message type without restart |
Install
| Channel | Command | Notes |
|---|---|---|
| PyPI | pip install pain001-lsp |
Pulls in pain001 >= 0.0.53 + pygls |
| Source | git clone https://github.com/sebastienrousseau/pain001-lsp && cd pain001-lsp && poetry install |
For development |
| Docker (GHCR) | docker pull ghcr.io/sebastienrousseau/pain001-lsp:latest |
Multi-arch (linux/amd64, linux/arm64); runs pain001-lsp over stdio |
Requires Python 3.10 or later. Works on macOS, Linux, and Windows.
Verify the installation:
python -c "import pain001_lsp; print('pain001-lsp', pain001_lsp.__version__)"
# -> pain001-lsp 0.0.53
Using an isolated virtual environment (recommended)
python -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
python -m pip install -U pain001-lsp
Quick start
The package installs a pain001-lsp console entry point that starts
the language server over stdio:
pain001-lsp
# -> (waiting on stdin for LSP JSON-RPC)
The command speaks LSP on stdin/stdout — it is meant to be launched by your editor's LSP client, not used interactively. Wire it up (Editor wiring) and open any pain.001 payment-data JSON file; diagnostics, completion, hover, formatting, and the outline pane all light up as you type.
Features
For payment-data JSON files (a JSON array of flat records, or a single record object treated as one record):
| LSP method | Behaviour |
|---|---|
textDocument/publishDiagnostics |
Schema + IBAN/BIC validation on open and on every change; malformed JSON yields a single syntax diagnostic at the offending position |
textDocument/completion |
Every input field (description as detail) + every supported pain.001 / pain.008 message type |
textDocument/hover |
Schema description for the field under the cursor |
textDocument/codeAction |
"Add missing required fields" quick-fix on the record under the cursor, with type-appropriate placeholders ("", 0, false, [], {}) |
textDocument/formatting |
Two-space JSON pretty-print with a trailing newline; idempotent (returns no edits when already formatted); leaves malformed JSON untouched so diagnostics still surface the error |
textDocument/documentSymbol |
One DocumentSymbol per top-level record so editors populate the outline pane, jump-to-record, and code-fold individual records (name = id, detail = payment_id) |
The default message type is pain.001.001.09 (Customer Credit Transfer
Initiation V09). Override per-workspace at startup with
initializationOptions: {"messageType": "pain.001.001.11"}, or
hot-swap at runtime via workspace/didChangeConfiguration with
{"pain001": {"messageType": "pain.001.001.11"}}.
The feature logic lives in pure, importable helpers
(compute_diagnostics, completion_items, hover_text,
missing_required_fields, build_insert_text,
_record_close_positions, _normalise_records); the LSP handlers are
thin glue that map plain dicts to lsprotocol types.
Editor wiring
Register pain001-lsp as the server cmd for JSON files in your
editor's LSP client.
Neovim (built-in vim.lsp.config)
vim.lsp.config["pain001"] = {
cmd = { "pain001-lsp" },
filetypes = { "json" },
root_markers = { ".git" },
init_options = { messageType = "pain.001.001.09" },
}
vim.lsp.enable("pain001")
VS Code (bundled scaffold)
A TypeScript language-client scaffold ships at
editors/vscode/ — runnable straight from source:
cd editors/vscode
npm install
npm run compile
# Press F5 in VS Code to launch an Extension Development Host.
pain001.serverCommand (default pain001-lsp) and pain001.messageType
(default pain.001.001.09) are exposed as settings.
Helix / Emacs / generic LSP
Any client that can spawn a stdio language server will work. The
command is pain001-lsp, the filetype is json, and
initializationOptions accepts {"messageType": "..."}.
Using the helpers
Because the feature logic is pure, you can call it directly — no editor or server process required. This is exactly what the server runs on each edit:
import json
from pain001_lsp.server import (
completion_items,
compute_diagnostics,
hover_text,
missing_required_fields,
)
# Minimal valid record (the schema accepts these required fields).
valid_record = {
"id": "MSG-0001",
"date": "2026-01-15T10:30:00",
"nb_of_txs": 1,
"ctrl_sum": 100.00,
"initiator_name": "Acme Embedded Finance Ltd",
"payment_information_id": "PMT-INFO-0001",
"payment_method": "TRF",
"batch_booking": False,
"service_level_code": "SEPA",
"requested_execution_date": "2026-01-20",
"debtor_name": "Acme Embedded Finance Ltd",
"debtor_account_IBAN": "DE89370400440532013000",
"debtor_agent_BIC": "DEUTDEFFXXX",
"charge_bearer": "SLEV",
"payment_id": "PAY-0001",
"payment_amount": 100.00,
"currency": "EUR",
"creditor_agent_BIC": "NWBKGB2LXXX",
"creditor_name": "National Westminster Bank",
"creditor_account_IBAN": "GB29NWBK60161331926819",
"remittance_information": "Invoice 0001",
}
# 1. A complete record produces no diagnostics.
assert compute_diagnostics(json.dumps([valid_record])) == []
# 2. Missing required fields surface as errors.
diagnostics = compute_diagnostics(json.dumps([{"id": "ONLY-ID"}]))
print(len(diagnostics), "issue(s)")
# -> e.g. "5 issue(s)"
# 3. An invalid IBAN is flagged.
print(compute_diagnostics(
json.dumps([{"debtor_account_IBAN": "INVALID"}])
)[:1])
# -> [{"line": ..., "character": ..., "severity": "warning",
# "message": "debtor_account_IBAN: invalid IBAN"}]
# 4. Quick-fix data: list missing fields, render insertion text.
missing = missing_required_fields({"id": "ONLY-ID"})
print(missing[:3]) # -> e.g. ['date', 'nb_of_txs', 'ctrl_sum']
# 5. Completion + hover.
items = completion_items()
print(len(items), "items, first:", items[0]["label"])
# -> e.g. "47 items, first: id"
print(hover_text("debtor_account_IBAN"))
# -> the field's schema description
print(hover_text("nope"))
# -> None
Each diagnostic is a plain dict —
{"line": int, "character": int, "severity": "error"|"warning", "message": str} —
which the server maps to lsprotocol.Diagnostic before publishing.
The runnable version of this snippet lives in
examples/01_lsp_helpers.py. See also
02_quick_fix.py (the code-action surface)
and 03_configure_message_type.py
(overriding the default message type via initializationOptions).
The pain001 suite
pain001-lsp is part of a set of independently installable packages
built around the pain001
library — pick whichever ones your stack needs:
| Package | Role |
|---|---|
pain001 |
Core library + CLI + FastAPI REST API |
pain001-mcp |
Model Context Protocol server (for AI agents) |
pain001-lsp |
Language Server Protocol server (this package) |
flowchart LR
A["Editor (VS Code / Neovim / …)"] -->|LSP over stdio| B["pain001-lsp"]
B -->|compute_diagnostics / completion_items / hover_text / formatting / document_symbol| C["pain001"]
C -->|schema + IBAN/BIC validation| B
B -->|diagnostics · completion · hover · code action · format · outline| A
When not to use pain001-lsp
- You're not editing JSON payment data. The server targets the
JSON record format consumed by the
pain001generator; if you're authoring the underlying XML directly, use a generic XSD-aware language server. - You need CSV diagnostics in the editor. The CSV diagnostic engine
ships in-tree under
pain001[lsp](pain001-lsp-builtin); this standalone server focuses on the richer JSON surface. - You want agent tools, not editor diagnostics. Use
pain001-mcp— it speaks the Model Context Protocol to AI assistants.
Development
pain001-lsp uses Poetry and
mise.
git clone https://github.com/sebastienrousseau/pain001-lsp.git
cd pain001-lsp
mise install
poetry install
A Makefile orchestrates the quality gates (kept in lockstep with CI):
| Target | What it runs |
|---|---|
make check |
All gates (REQUIRED before commit) |
make test |
pytest --cov=pain001_lsp --cov-branch --cov-fail-under=100 |
make lint |
ruff check + ruff format --check |
make type-check |
mypy --strict |
make examples |
Run the three example scripts |
Current state (v0.0.53): 81 tests passing, 100% line + branch
coverage against a 100% enforced floor, mypy --strict clean,
interrogate 100% docstring coverage.
Security
- No filesystem writes. The server reads from the editor's in-memory document buffer; no scratch files, no temp directories.
- JSON parsing uses the stdlib
jsonmodule on already-quoted text from the editor — noeval, no shelling out, no XML. - Validation failures are returned as
lsprotocol.Diagnosticobjects with no stack traces, so the editor never sees an internal path or exception message. - Dependencies are pinned via
poetry.lockand audited bypip-auditand Bandit in CI.
To report a vulnerability, please use GitHub private vulnerability reporting rather than a public issue.
Documentation
- Runnable examples:
examples/ - VS Code scaffold:
editors/vscode/ - Release history: CHANGELOG.md
- Core library docs: docs.pain001.com
- LSP specification: microsoft.github.io/language-server-protocol
Contributing
Contributions are welcome — see the
contributing instructions.
Thanks to all the
contributors
who have helped build pain001-lsp.
License
Licensed under the Apache License, Version 2.0.
Built on pygls and
lsprotocol by the
Open Law Library, and on the core
pain001 library that
powers the validators and schemas.
Any contribution submitted for inclusion shall be licensed as above, without additional terms.
pain001.com · PyPI · GitHub
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pain001_lsp-0.0.53.tar.gz.
File metadata
- Download URL: pain001_lsp-0.0.53.tar.gz
- Upload date:
- Size: 23.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f4b21f1f1b6f626a364e691d00745a520711c45813543b4f71cf74d3a7ea593
|
|
| MD5 |
49d7c3dd312fb641dd9296fdd028dac6
|
|
| BLAKE2b-256 |
e553deffac40082ca1d76f4eca28d2c6d0344cbd62b92bb0fcf319da6e71af9b
|
Provenance
The following attestation bundles were made for pain001_lsp-0.0.53.tar.gz:
Publisher:
release.yml on sebastienrousseau/pain001-lsp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pain001_lsp-0.0.53.tar.gz -
Subject digest:
4f4b21f1f1b6f626a364e691d00745a520711c45813543b4f71cf74d3a7ea593 - Sigstore transparency entry: 1880799949
- Sigstore integration time:
-
Permalink:
sebastienrousseau/pain001-lsp@78b25f9e87d7a98b8c8de8b7826e82ae86cadbc3 -
Branch / Tag:
refs/tags/v0.0.53 - Owner: https://github.com/sebastienrousseau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@78b25f9e87d7a98b8c8de8b7826e82ae86cadbc3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pain001_lsp-0.0.53-py3-none-any.whl.
File metadata
- Download URL: pain001_lsp-0.0.53-py3-none-any.whl
- Upload date:
- Size: 20.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1bcb04c5af0fc1fec5c8d8ee6dc4701b8b73fa3fdc7d79263e7cd5e2ae77c25b
|
|
| MD5 |
71e09a391a9845f88c213af97370c5e0
|
|
| BLAKE2b-256 |
08f962d348f9913455e473dfc4d9657091742206e174a62778d9781aa56b6571
|
Provenance
The following attestation bundles were made for pain001_lsp-0.0.53-py3-none-any.whl:
Publisher:
release.yml on sebastienrousseau/pain001-lsp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pain001_lsp-0.0.53-py3-none-any.whl -
Subject digest:
1bcb04c5af0fc1fec5c8d8ee6dc4701b8b73fa3fdc7d79263e7cd5e2ae77c25b - Sigstore transparency entry: 1880800154
- Sigstore integration time:
-
Permalink:
sebastienrousseau/pain001-lsp@78b25f9e87d7a98b8c8de8b7826e82ae86cadbc3 -
Branch / Tag:
refs/tags/v0.0.53 - Owner: https://github.com/sebastienrousseau
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@78b25f9e87d7a98b8c8de8b7826e82ae86cadbc3 -
Trigger Event:
push
-
Statement type: