mcbp 0.2.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. mcbp-0.2.4/.gitignore +27 -0
  2. mcbp-0.2.4/LICENSE +21 -0
  3. mcbp-0.2.4/PKG-INFO +177 -0
  4. mcbp-0.2.4/README.md +152 -0
  5. mcbp-0.2.4/deploy/README.md +236 -0
  6. mcbp-0.2.4/deploy/client-kit/README.md +62 -0
  7. mcbp-0.2.4/deploy/client-kit/setup-codex.cmd +59 -0
  8. mcbp-0.2.4/deploy/client-kit/trust-cert.cmd +41 -0
  9. mcbp-0.2.4/deploy/mcbp-mcp.env.example +77 -0
  10. mcbp-0.2.4/deploy/mcbp-mcp@.service +37 -0
  11. mcbp-0.2.4/deploy/nginx-mcp.conf +91 -0
  12. mcbp-0.2.4/deploy/portable/build.ps1 +111 -0
  13. mcbp-0.2.4/deploy/portable/payload/README.md +99 -0
  14. mcbp-0.2.4/deploy/portable/payload/make-cert.ps1 +140 -0
  15. mcbp-0.2.4/deploy/portable/payload/new-token.cmd +15 -0
  16. mcbp-0.2.4/deploy/portable/payload/serve.py +142 -0
  17. mcbp-0.2.4/deploy/portable/payload/server.env +73 -0
  18. mcbp-0.2.4/deploy/portable/payload/start.cmd +11 -0
  19. mcbp-0.2.4/deploy/portable/payload/tokens.example.toml +20 -0
  20. mcbp-0.2.4/deploy/portable/payload/update.cmd +16 -0
  21. mcbp-0.2.4/deploy/portable/payload//320/222/320/241/320/242/320/220/320/235/320/236/320/222/320/233/320/225/320/235/320/235/320/257.html +470 -0
  22. mcbp-0.2.4/pyproject.toml +61 -0
  23. mcbp-0.2.4/server.json +77 -0
  24. mcbp-0.2.4/src/mcbp_mcp_server/__init__.py +19 -0
  25. mcbp-0.2.4/src/mcbp_mcp_server/__main__.py +5 -0
  26. mcbp-0.2.4/src/mcbp_mcp_server/credentials.py +298 -0
  27. mcbp-0.2.4/src/mcbp_mcp_server/errors.py +52 -0
  28. mcbp-0.2.4/src/mcbp_mcp_server/http.py +317 -0
  29. mcbp-0.2.4/src/mcbp_mcp_server/registry.py +112 -0
  30. mcbp-0.2.4/src/mcbp_mcp_server/server.py +206 -0
  31. mcbp-0.2.4/tests/test_credentials.py +650 -0
  32. mcbp-0.2.4/tests/test_http.py +110 -0
  33. mcbp-0.2.4/tests/test_registry.py +210 -0
  34. mcbp-0.2.4/tests/test_server.py +220 -0
mcbp-0.2.4/.gitignore ADDED
@@ -0,0 +1,27 @@
1
+ # Secrets — NEVER commit
2
+ .env
3
+ .env.*
4
+ *.key
5
+ *.pem
6
+ *.crt
7
+
8
+ # Python
9
+ __pycache__/
10
+ *.pyc
11
+ *.pyo
12
+ .venv/
13
+ .venv-*/
14
+ *.egg-info/
15
+ dist/
16
+ build/
17
+
18
+ # Tooling caches
19
+ .pytest_cache/
20
+ .ruff_cache/
21
+ .mypy_cache/
22
+
23
+ # IDE / OS
24
+ .idea/
25
+ .vscode/
26
+ .DS_Store
27
+ Thumbs.db
mcbp-0.2.4/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MCBP.PLUS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
mcbp-0.2.4/PKG-INFO ADDED
@@ -0,0 +1,177 @@
1
+ Metadata-Version: 2.5
2
+ Name: mcbp
3
+ Version: 0.2.4
4
+ Summary: Standalone MCP server exposing BAS MCBP_AI (mcbp.plus)
5
+ Project-URL: Homepage, https://mcbp.plus
6
+ Project-URL: Repository, https://github.com/kzavrazhnyi/mcbp-mcp-server
7
+ Author: MCBP.PLUS
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: 1c,bas,erp,mcbp,mcp
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: anyio>=4.0
13
+ Requires-Dist: mcbp-core<0.2,>=0.1.1
14
+ Requires-Dist: mcp>=2.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
17
+ Requires-Dist: pytest>=8.3; extra == 'dev'
18
+ Requires-Dist: respx>=0.21; extra == 'dev'
19
+ Requires-Dist: ruff>=0.7; extra == 'dev'
20
+ Requires-Dist: starlette>=0.40; extra == 'dev'
21
+ Provides-Extra: http
22
+ Requires-Dist: starlette>=0.40; extra == 'http'
23
+ Requires-Dist: uvicorn>=0.30; extra == 'http'
24
+ Description-Content-Type: text/markdown
25
+
26
+ <!-- mcp-name: io.github.kzavrazhnyi/mcbp-ai -->
27
+
28
+ # mcbp-ai — MCP server for BAS / 1C (MCBP+)
29
+
30
+ Read your BAS (1C) database from any MCP client. Catalogs, documents, register balances and records,
31
+ and the full configuration metadata tree — exposed as MCP tools over the `MCBP_AI` HTTP service.
32
+
33
+ **Read-only by default.** None of the tools available out of the box modifies your data.
34
+
35
+ **Model-agnostic.** MCP is a vendor-neutral standard, so this server works with **Claude Desktop /
36
+ Code, ChatGPT desktop, Gemini, Microsoft and GitHub Copilot, Cursor, Windsurf, VS Code and Zed** —
37
+ it serves `tools/list` and executes `tools/call` without knowing which model is asking. The
38
+ transport is **stdio**, so the client launches the process locally; browser-based clients would
39
+ need a remote HTTP server, which this one does not expose.
40
+
41
+ > ### ⚠ This server needs a server-side component that is not in this repository
42
+ >
43
+ > `mcbp-ai` is a client for the **`MCBP_AI` HTTP service** — a BSL module (a common module plus an
44
+ > HTTP service definition) that must be installed into your BAS/1C configuration through
45
+ > Конфігуратор. **That module is licensed separately and is not part of this repository.**
46
+ >
47
+ > Without it this server starts, connects, and every route answers `404`. If you want to run it
48
+ > against your own base, contact MCBP.PLUS to obtain the `MCBP_AI` module.
49
+ >
50
+ > The Python code here — the MCP adapter and the shared BAS client — is open source and complete.
51
+
52
+ The MCBP+ configuration and the `MCBP_AI` service module are proprietary works of
53
+ **[MCBP.PLUS](https://mcbp.plus)** and are distributed separately from this repository. This MIT
54
+ licence covers the Python packages only.
55
+
56
+ ## Why this exists
57
+
58
+ BAS/1C holds the data, but it is not reachable from an LLM: the platform speaks its own query
59
+ language, metadata names are inherited Russian identifiers while synonyms are Ukrainian, and a raw
60
+ OData feed is both heavy and hostile to a model (opaque errors, no self-correction path).
61
+
62
+ `MCBP_AI` solves that on the BAS side — canonical English field names in list rows, compact
63
+ responses, and errors that name the offending field verbatim so a model can fix its own call. This
64
+ server is the thin MCP adapter in front of it.
65
+
66
+ ## Tools
67
+
68
+ 10 read tools are always registered. Three write tools appear only when `MCP_ALLOW_WRITE` is on.
69
+
70
+ | Tool | What it does |
71
+ |---|---|
72
+ | `list_metadata` | Every object of a metadata kind (names + synonyms) — start here |
73
+ | `describe_metadata` | Full attribute tree of one object, incl. tabular sections |
74
+ | `search_catalog` | Substring search over a catalog by name |
75
+ | `filter_catalog` | Filter/sort/aggregate a catalog by any field (`agg`, `groupby`) |
76
+ | `get_documents` | Documents over a period, with filters, sorting and aggregation |
77
+ | `get_schema` | Data structure of one document type |
78
+ | `get_object` | **All** attribute values of one record + its tabular sections |
79
+ | `get_register_balance` | Accumulation-register balance (native query) |
80
+ | `get_register_records` | Raw rows of an information or accumulation register |
81
+ | `health` | Service ping — reports whether the infobase key matches |
82
+ | `write_object` *(gated)* | Create/update an object through MCBP Plus conversion rules |
83
+ | `patch_object` *(gated)* | Update header attributes of an existing object natively |
84
+ | `save_context` *(gated)* | Push a conversation turn upstream (skeleton) |
85
+
86
+ The model is expected to walk `list_metadata → describe_metadata → search/filter → get_object`.
87
+ Errors are deliberately not swallowed: an unknown field comes back as `BAD_PARAMETER` naming that
88
+ field, which is how the model corrects itself.
89
+
90
+ ## Install
91
+
92
+ Requires **Python 3.11+**.
93
+
94
+ ```bash
95
+ pip install mcbp
96
+ ```
97
+
98
+ ## Configure
99
+
100
+ ### Claude Desktop
101
+
102
+ `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or
103
+ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "mcbp-ai": {
109
+ "command": "python",
110
+ "args": ["-m", "mcbp_mcp_server"],
111
+ "env": {
112
+ "ONEC_BASE_URL": "http://host/base/hs/mcbp_ai",
113
+ "ONEC_USER": "ai_service",
114
+ "ONEC_PASSWORD": "***"
115
+ }
116
+ }
117
+ }
118
+ }
119
+ ```
120
+
121
+ ### Claude Code
122
+
123
+ ```bash
124
+ claude mcp add mcbp-ai --env ONEC_BASE_URL=http://host/base/hs/mcbp_ai \
125
+ --env ONEC_USER=ai_service \
126
+ --env ONEC_PASSWORD=*** \
127
+ -- python -m mcbp_mcp_server
128
+ ```
129
+
130
+ ## Environment
131
+
132
+ | Variable | Required | Default | Purpose |
133
+ |---|---|---|---|
134
+ | `ONEC_BASE_URL` | yes | — | `http(s)://<host>/<base>/hs/mcbp_ai` — see the note below |
135
+ | `ONEC_USER` | yes | — | HTTP Basic user of the BAS publication |
136
+ | `ONEC_PASSWORD` | yes | — | Password. **May be empty**, but the variable must be present |
137
+ | `ONEC_TIMEOUT` | no | `30` | Request timeout, seconds |
138
+ | `ONEC_POOL_MAX` | no | `10` | Connection pool size |
139
+ | `ONEC_VERIFY_SSL` | no | `1` | `0` for self-signed intranet publications |
140
+ | `MCP_ALLOW_WRITE` | no | `0` | `1` enables the three write tools |
141
+
142
+ ### `ONEC_BASE_URL` — leave off the `/ai/v1`
143
+
144
+ The client appends `/ai/v1/...` itself, so the base URL stops at the service name:
145
+
146
+ ```
147
+ http://localhost/mybase/hs/mcbp_ai ← correct
148
+ http://localhost/mybase/hs/mcbp_ai/ai/v1/ ← also accepted (normalized away)
149
+ ```
150
+
151
+ Note that the published path genuinely repeats the segment: `rootUrl` is `mcbp_ai` in `default.vrd`,
152
+ and the service's own URL templates start with `ai/v1`.
153
+
154
+ ## Security
155
+
156
+ - Read-only unless you deliberately set `MCP_ALLOW_WRITE=1`. The write tools are not merely hidden
157
+ — they are never registered, so they cannot be invoked.
158
+ - Credentials live only in the MCP client's env block; nothing is written to disk by this server.
159
+ - The BAS side adds its own checks: the publication's user rights, plus an infobase-key check. If
160
+ the key does not match, `health` reports `key: false` and every other route answers
161
+ `403 KEY_MISMATCH`.
162
+ - `write_object` additionally requires the MCBP Plus extension and a configured conversion rule;
163
+ without one it returns `422 CONVERSION_NOT_CONFIGURED` and writes nothing.
164
+
165
+ ## Troubleshooting
166
+
167
+ | Symptom | Cause |
168
+ |---|---|
169
+ | Every route `404`, `health` also fails | `ONEC_BASE_URL` wrong, or the `MCBP_AI` module is not installed in the base |
170
+ | Every route `403 KEY_MISMATCH` | Infobase key mismatch — check `health`, fix the publication key |
171
+ | `ONEC_PASSWORD is required` | The variable is absent. An empty value is fine; an absent one is not |
172
+ | `501 PLUS_REQUIRED` | Only `write_object` raises it — that base has no MCBP Plus |
173
+ | Model says a register is unreadable | Information registers have no `Ref`; use `get_register_records` |
174
+
175
+ ## License
176
+
177
+ See `LICENSE`.
mcbp-0.2.4/README.md ADDED
@@ -0,0 +1,152 @@
1
+ <!-- mcp-name: io.github.kzavrazhnyi/mcbp-ai -->
2
+
3
+ # mcbp-ai — MCP server for BAS / 1C (MCBP+)
4
+
5
+ Read your BAS (1C) database from any MCP client. Catalogs, documents, register balances and records,
6
+ and the full configuration metadata tree — exposed as MCP tools over the `MCBP_AI` HTTP service.
7
+
8
+ **Read-only by default.** None of the tools available out of the box modifies your data.
9
+
10
+ **Model-agnostic.** MCP is a vendor-neutral standard, so this server works with **Claude Desktop /
11
+ Code, ChatGPT desktop, Gemini, Microsoft and GitHub Copilot, Cursor, Windsurf, VS Code and Zed** —
12
+ it serves `tools/list` and executes `tools/call` without knowing which model is asking. The
13
+ transport is **stdio**, so the client launches the process locally; browser-based clients would
14
+ need a remote HTTP server, which this one does not expose.
15
+
16
+ > ### ⚠ This server needs a server-side component that is not in this repository
17
+ >
18
+ > `mcbp-ai` is a client for the **`MCBP_AI` HTTP service** — a BSL module (a common module plus an
19
+ > HTTP service definition) that must be installed into your BAS/1C configuration through
20
+ > Конфігуратор. **That module is licensed separately and is not part of this repository.**
21
+ >
22
+ > Without it this server starts, connects, and every route answers `404`. If you want to run it
23
+ > against your own base, contact MCBP.PLUS to obtain the `MCBP_AI` module.
24
+ >
25
+ > The Python code here — the MCP adapter and the shared BAS client — is open source and complete.
26
+
27
+ The MCBP+ configuration and the `MCBP_AI` service module are proprietary works of
28
+ **[MCBP.PLUS](https://mcbp.plus)** and are distributed separately from this repository. This MIT
29
+ licence covers the Python packages only.
30
+
31
+ ## Why this exists
32
+
33
+ BAS/1C holds the data, but it is not reachable from an LLM: the platform speaks its own query
34
+ language, metadata names are inherited Russian identifiers while synonyms are Ukrainian, and a raw
35
+ OData feed is both heavy and hostile to a model (opaque errors, no self-correction path).
36
+
37
+ `MCBP_AI` solves that on the BAS side — canonical English field names in list rows, compact
38
+ responses, and errors that name the offending field verbatim so a model can fix its own call. This
39
+ server is the thin MCP adapter in front of it.
40
+
41
+ ## Tools
42
+
43
+ 10 read tools are always registered. Three write tools appear only when `MCP_ALLOW_WRITE` is on.
44
+
45
+ | Tool | What it does |
46
+ |---|---|
47
+ | `list_metadata` | Every object of a metadata kind (names + synonyms) — start here |
48
+ | `describe_metadata` | Full attribute tree of one object, incl. tabular sections |
49
+ | `search_catalog` | Substring search over a catalog by name |
50
+ | `filter_catalog` | Filter/sort/aggregate a catalog by any field (`agg`, `groupby`) |
51
+ | `get_documents` | Documents over a period, with filters, sorting and aggregation |
52
+ | `get_schema` | Data structure of one document type |
53
+ | `get_object` | **All** attribute values of one record + its tabular sections |
54
+ | `get_register_balance` | Accumulation-register balance (native query) |
55
+ | `get_register_records` | Raw rows of an information or accumulation register |
56
+ | `health` | Service ping — reports whether the infobase key matches |
57
+ | `write_object` *(gated)* | Create/update an object through MCBP Plus conversion rules |
58
+ | `patch_object` *(gated)* | Update header attributes of an existing object natively |
59
+ | `save_context` *(gated)* | Push a conversation turn upstream (skeleton) |
60
+
61
+ The model is expected to walk `list_metadata → describe_metadata → search/filter → get_object`.
62
+ Errors are deliberately not swallowed: an unknown field comes back as `BAD_PARAMETER` naming that
63
+ field, which is how the model corrects itself.
64
+
65
+ ## Install
66
+
67
+ Requires **Python 3.11+**.
68
+
69
+ ```bash
70
+ pip install mcbp
71
+ ```
72
+
73
+ ## Configure
74
+
75
+ ### Claude Desktop
76
+
77
+ `%APPDATA%\Claude\claude_desktop_config.json` (Windows) or
78
+ `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
79
+
80
+ ```json
81
+ {
82
+ "mcpServers": {
83
+ "mcbp-ai": {
84
+ "command": "python",
85
+ "args": ["-m", "mcbp_mcp_server"],
86
+ "env": {
87
+ "ONEC_BASE_URL": "http://host/base/hs/mcbp_ai",
88
+ "ONEC_USER": "ai_service",
89
+ "ONEC_PASSWORD": "***"
90
+ }
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Claude Code
97
+
98
+ ```bash
99
+ claude mcp add mcbp-ai --env ONEC_BASE_URL=http://host/base/hs/mcbp_ai \
100
+ --env ONEC_USER=ai_service \
101
+ --env ONEC_PASSWORD=*** \
102
+ -- python -m mcbp_mcp_server
103
+ ```
104
+
105
+ ## Environment
106
+
107
+ | Variable | Required | Default | Purpose |
108
+ |---|---|---|---|
109
+ | `ONEC_BASE_URL` | yes | — | `http(s)://<host>/<base>/hs/mcbp_ai` — see the note below |
110
+ | `ONEC_USER` | yes | — | HTTP Basic user of the BAS publication |
111
+ | `ONEC_PASSWORD` | yes | — | Password. **May be empty**, but the variable must be present |
112
+ | `ONEC_TIMEOUT` | no | `30` | Request timeout, seconds |
113
+ | `ONEC_POOL_MAX` | no | `10` | Connection pool size |
114
+ | `ONEC_VERIFY_SSL` | no | `1` | `0` for self-signed intranet publications |
115
+ | `MCP_ALLOW_WRITE` | no | `0` | `1` enables the three write tools |
116
+
117
+ ### `ONEC_BASE_URL` — leave off the `/ai/v1`
118
+
119
+ The client appends `/ai/v1/...` itself, so the base URL stops at the service name:
120
+
121
+ ```
122
+ http://localhost/mybase/hs/mcbp_ai ← correct
123
+ http://localhost/mybase/hs/mcbp_ai/ai/v1/ ← also accepted (normalized away)
124
+ ```
125
+
126
+ Note that the published path genuinely repeats the segment: `rootUrl` is `mcbp_ai` in `default.vrd`,
127
+ and the service's own URL templates start with `ai/v1`.
128
+
129
+ ## Security
130
+
131
+ - Read-only unless you deliberately set `MCP_ALLOW_WRITE=1`. The write tools are not merely hidden
132
+ — they are never registered, so they cannot be invoked.
133
+ - Credentials live only in the MCP client's env block; nothing is written to disk by this server.
134
+ - The BAS side adds its own checks: the publication's user rights, plus an infobase-key check. If
135
+ the key does not match, `health` reports `key: false` and every other route answers
136
+ `403 KEY_MISMATCH`.
137
+ - `write_object` additionally requires the MCBP Plus extension and a configured conversion rule;
138
+ without one it returns `422 CONVERSION_NOT_CONFIGURED` and writes nothing.
139
+
140
+ ## Troubleshooting
141
+
142
+ | Symptom | Cause |
143
+ |---|---|
144
+ | Every route `404`, `health` also fails | `ONEC_BASE_URL` wrong, or the `MCBP_AI` module is not installed in the base |
145
+ | Every route `403 KEY_MISMATCH` | Infobase key mismatch — check `health`, fix the publication key |
146
+ | `ONEC_PASSWORD is required` | The variable is absent. An empty value is fine; an absent one is not |
147
+ | `501 PLUS_REQUIRED` | Only `write_object` raises it — that base has no MCBP Plus |
148
+ | Model says a register is unreadable | Information registers have no `Ref`; use `get_register_records` |
149
+
150
+ ## License
151
+
152
+ See `LICENSE`.
@@ -0,0 +1,236 @@
1
+ # Deploying `mcbp-ai` as a remote MCP server
2
+
3
+ Runs the same server the stdio entry point runs, over Streamable HTTP. One process per BAS base.
4
+
5
+ Two stages, and only the first is being done now:
6
+
7
+ | | Stage 1 — internal network | Stage 2 — external server, public IP |
8
+ |---|---|---|
9
+ | Client reaches | the process, `http://`, directly | nginx, `https://` |
10
+ | Process listens on | the LAN interface | `127.0.0.1` |
11
+ | Access control | the network boundary, nothing else | TLS + token or IP allow-list in nginx |
12
+ | Files used | `mcbp-mcp@.service`, `mcbp-mcp.env.example` | the same two + `nginx-mcp.conf` |
13
+
14
+ Stage 2 is a config delta on top of stage 1, not a redeployment: same venv, same unit, same env
15
+ files with three values changed. It is written out in section 6.
16
+
17
+ ## 0. Which clients can reach an internal endpoint
18
+
19
+ The rule is **where the MCP client process runs**, not which model is behind it.
20
+
21
+ - **Runs on a machine inside the network → works in stage 1.** Claude Code is the case at hand;
22
+ so is any in-house application that speaks MCP itself, whatever model it drives (OpenAI
23
+ included).
24
+ - **Hosted connector surfaces → cannot work in stage 1.** claude.ai, Claude Desktop and ChatGPT
25
+ connectors do not connect from the user's machine: the provider's infrastructure does, from the
26
+ public internet (Anthropic's egress is `160.79.104.0/21`). Such a client cannot see an internal
27
+ host at all. That is architecture, not configuration — it changes only in stage 2.
28
+
29
+ ---
30
+
31
+ # Stage 1: internal network
32
+
33
+ ## 1. Install
34
+
35
+ ```bash
36
+ sudo useradd --system --home /opt/mcbp-mcp --shell /usr/sbin/nologin mcbp
37
+ sudo mkdir -p /opt/mcbp-mcp && sudo chown mcbp:mcbp /opt/mcbp-mcp
38
+ sudo -u mcbp python3.11 -m venv /opt/mcbp-mcp/.venv
39
+ ```
40
+
41
+ The distribution is named `mcbp` and is published on **TestPyPI**. Release `0.2.0` must be
42
+ uploaded there before this command can work — `mcbp-core` first, then `mcbp`; a published version
43
+ is immutable, which is why the HTTP transport ships as a new version rather than a re-upload.
44
+
45
+ ```bash
46
+ sudo -u mcbp /opt/mcbp-mcp/.venv/bin/pip install \
47
+ --index-url https://test.pypi.org/simple/ \
48
+ --extra-index-url https://pypi.org/simple/ \
49
+ "mcbp[http]==0.2.0"
50
+ ```
51
+
52
+ - **`--extra-index-url https://pypi.org/simple/` is mandatory.** The runtime dependencies —
53
+ httpx, mcp, starlette, uvicorn — are not on TestPyPI, so resolution fails without it. That is
54
+ a property of TestPyPI, not a defect of the package.
55
+ - **Pin the version.** A bare `mcbp[http]` resolves to whatever is newest on TestPyPI, which is
56
+ not always what was tested. `0.2.0` is the first release carrying the `mcbp-http` entry point
57
+ and the `http` extra; 0.1.0 predates the HTTP transport and gives a stdio-only server.
58
+ - **TestPyPI is a sandbox.** It promises no durability and its packages can be pruned. Fine for
59
+ the internal stage; publishing to the production PyPI is a stage-2 item, before the endpoint
60
+ faces a public IP.
61
+
62
+ **Fallback — install from the checkout.** For a machine with no access to TestPyPI, or to test a
63
+ change that is not released yet:
64
+
65
+ ```bash
66
+ sudo -u mcbp /opt/mcbp-mcp/.venv/bin/pip install -e /srv/mcbp-mcp-server/mcbp-core
67
+ sudo -u mcbp /opt/mcbp-mcp/.venv/bin/pip install -e "/srv/mcbp-mcp-server/mcp-server[http]"
68
+ ```
69
+
70
+ Here — and only here — `mcbp-core` must go **first and editable (`-e`)**. A plain `pip install`
71
+ leaves a snapshot copy in `site-packages`, and later edits to the checkout then silently do
72
+ nothing. That failure has already happened on this box, in the backend's venv (19.08).
73
+
74
+ **Keep this venv separate from the backend's** at `/opt/mcbp-ai/backend/.venv`. The two install
75
+ the same packages at different versions and share a machine, nothing else.
76
+
77
+ One venv serves every base: updating is one `pip install` plus
78
+ `sudo systemctl restart 'mcbp-mcp@*'`.
79
+
80
+ ## 2. One env file per base
81
+
82
+ ```bash
83
+ sudo mkdir -p /etc/mcbp-mcp
84
+ sudo cp mcbp-mcp.env.example /etc/mcbp-mcp/prod.env
85
+ sudo chmod 600 /etc/mcbp-mcp/prod.env && sudo chown root:root /etc/mcbp-mcp/prod.env
86
+ sudo -e /etc/mcbp-mcp/prod.env
87
+ ```
88
+
89
+ The file name is the systemd instance name: `/etc/mcbp-mcp/prod.env` → `mcbp-mcp@prod`. Repeat
90
+ per base, changing `ONEC_BASE_URL`, the credentials and — mandatory — `MCP_HTTP_PORT`.
91
+
92
+ For stage 1 set `MCP_HTTP_HOST` to the LAN address (or `0.0.0.0`) and leave
93
+ `MCP_HTTP_ALLOWED_HOSTS` **empty**.
94
+
95
+ Read the comments in that file. Three of them mark mistakes that have already cost a live
96
+ round-trip each: the `/ai/v1` suffix, the absent `ONEC_PASSWORD`, the empty host allow-list.
97
+
98
+ ## 3. Run
99
+
100
+ ```bash
101
+ sudo cp mcbp-mcp@.service /etc/systemd/system/
102
+ sudo systemctl daemon-reload
103
+ sudo systemctl enable --now mcbp-mcp@prod mcbp-mcp@demo
104
+ ```
105
+
106
+ One unit file, any number of bases — the instance name after `@` picks the env file.
107
+
108
+ ```bash
109
+ systemctl status 'mcbp-mcp@*'
110
+ journalctl -u mcbp-mcp@prod -n 30 # startup logs the BAS base_url and the write flag
111
+ ```
112
+
113
+ Bring the first base up alone and verify it end to end before adding the rest.
114
+
115
+ ## 4. Verify
116
+
117
+ Two checks that answer different questions.
118
+
119
+ **a) The process is alive.** Says nothing about BAS — it is a static string:
120
+
121
+ ```bash
122
+ curl -s 127.0.0.1:8011/healthz # -> ok
123
+ ```
124
+
125
+ **b) The transport really speaks MCP.** A bare `GET /mcp` is expected to fail (400, no session),
126
+ so the check is an `initialize` round-trip. In stage 1 there is no TLS and no allow-list, so this
127
+ runs unchanged from the server or from a workstation — swap the host:
128
+
129
+ ```bash
130
+ curl -sS http://<server-lan-address>:8011/mcp \
131
+ -H 'content-type: application/json' \
132
+ -H 'accept: application/json, text/event-stream' \
133
+ -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
134
+ ```
135
+
136
+ A `serverInfo` with a non-empty `version` means the transport is live. It still says nothing
137
+ about BAS: the connection to the base is made lazily, on the first tool call.
138
+
139
+ **c) BAS is actually reachable.** Only a real tool call proves that. Register the server with a
140
+ client and confirm `tools/list` returns 10 tools (13 if `MCP_ALLOW_WRITE=1`), then call `health`.
141
+
142
+ ## 5. Connect a client
143
+
144
+ Claude Code, from a machine on the same network, connecting as the user's OWN BAS account:
145
+
146
+ ```bash
147
+ claude mcp add --transport http mcbp-prod http://<server-lan-address>:8011/mcp --header "Authorization: Basic $(printf '%s' 'user:password' | base64)"
148
+ ```
149
+
150
+ Each base is a separate entry with its own port.
151
+
152
+ **What the header does.** Every tool call from that client runs under the named BAS account: BAS
153
+ rights decide what it may read or write, and the BAS registration log names that person rather
154
+ than the service account. One process still serves one base — the header carries credentials
155
+ only, never a base selector.
156
+
157
+ **Without the header** the client falls back to the process's `ONEC_USER` (section 2). That is
158
+ still supported, and it is what stdio always does — but it puts every action under the one
159
+ service identity.
160
+
161
+ **A malformed header is rejected, never downgraded.** A non-Basic scheme, undecodable base64, or
162
+ a value that is not `user:password` answers `400 BAD_AUTHORIZATION`. A silent fallback to the
163
+ service account is exactly the failure this feature removes. A *wrong* password is not this case:
164
+ it reaches BAS and comes back as the normal BAS authentication failure.
165
+
166
+ **Where the password ends up.** In the client's own MCP config file, in clear text (base64 is not
167
+ encryption) — `~/.claude.json` for Claude Code. This differs from the web backend, where the
168
+ password is typed into a login form and lives only in that session's memory. Treat the config
169
+ file accordingly, and prefer a per-person BAS account over a shared one.
170
+
171
+ ## Security property of stage 1 — read this before opening the port
172
+
173
+ **The endpoint itself authenticates nobody.** It accepts every connection; what it does NOT do
174
+ is grant every connection the same power.
175
+
176
+ - **With an `Authorization` header** (section 5) the caller acts as their own BAS account. BAS
177
+ rights are then the access control, and the registration log names the person. The header is
178
+ passed through to BAS and verified there — this server never checks a password itself, so it
179
+ cannot be tricked into accepting one.
180
+ - **Without a header** the caller acts as the process's `ONEC_USER`, and every such action looks
181
+ identical in the BAS log. Keep that account low-privilege for exactly this reason.
182
+
183
+ `MCP_ALLOW_WRITE` remains a property of the PROCESS, not of the caller: a read-only instance
184
+ exposes the 10 read tools to everyone who reaches it, whatever account they present. Write
185
+ permission is therefore decided twice — by the instance, then by BAS rights.
186
+
187
+ What is still missing: **nothing stops an unauthorized person from reaching the port** and
188
+ falling back to the service account. Handing out "only the bases you are allowed" is a
189
+ convenience, not a control: ports are predictable, and someone inside the network who learns a
190
+ neighbouring base's URL simply adds it. So **the network boundary is still the control over WHO
191
+ connects** — the header changes what they can do once connected, not whether they may connect.
192
+ Both must be replaced before the endpoint gets a public IP.
193
+
194
+ ---
195
+
196
+ # Stage 2: external server with a public IP
197
+
198
+ What changes. Nothing is rebuilt.
199
+
200
+ 1. **nginx in front, terminating TLS.** Take the `location` blocks from `nginx-mcp.conf` — one
201
+ per instance, mapping a public path to that instance's port — into the TLS `server { }` block,
202
+ then reload. Do not drop `proxy_buffering off` or the 310 s timeouts: the first breaks SSE
203
+ streaming, the second lets nginx cut a long tool call.
204
+ 2. **`MCP_HTTP_HOST=127.0.0.1`** in every env file, then restart. The process must be reachable
205
+ only through the proxy; leaving it on the LAN interface would publish an unauthenticated
206
+ endpoint next to the guarded one.
207
+ 3. **`MCP_HTTP_ALLOWED_HOSTS=<public hostname>`** in every env file. This turns on DNS-rebinding
208
+ protection. It must agree with what nginx forwards, which is why the config keeps
209
+ `proxy_set_header Host $host`.
210
+ 4. **Access control, chosen deliberately** — the header of `nginx-mcp.conf` lists the options
211
+ (per-base token in a custom header via an nginx `map`, source-IP allow-list, OAuth) and what
212
+ each one is and is not good for. Whatever is chosen there decides WHO may connect; the BAS
213
+ account in the caller's `Authorization` header decides what they may then do. That header is
214
+ taken: nginx must forward it untouched and must not run `auth_basic` on these locations.
215
+ 5. **A publicly valid certificate** if hosted connector surfaces are to be clients (section 0).
216
+ 6. **Publish `mcbp` to the production PyPI** and reinstall from it. TestPyPI makes no durability
217
+ promise; a public deployment must not depend on a sandbox index.
218
+
219
+ ## Troubleshooting
220
+
221
+ | Symptom | Cause |
222
+ |---|---|
223
+ | Connection refused from a workstation, fine from the server | `MCP_HTTP_HOST` is still `127.0.0.1`. |
224
+ | `421 Invalid Host header` | `MCP_HTTP_ALLOWED_HOSTS` is set and disagrees with the Host the client sends. In stage 1 it should be empty; behind nginx, keep `proxy_set_header Host $host`. |
225
+ | `307` to `/mcp/` | Not from this server — the route matches `/mcp` exactly. Look for a proxy rewriting the path. |
226
+ | `400` on POST | Missing `mcp-session-id`, or `accept` without `text/event-stream`. |
227
+ | `400 BAD_AUTHORIZATION` | The `Authorization` header is not `Basic <base64 user:password>`. Rebuild it; the server deliberately does not fall back to the service account. |
228
+ | Tools answer with a BAS auth error | The credentials in the header are wrong for this base. The header reached BAS — that is the base rejecting them, not the transport. |
229
+ | Actions show as the service account in the BAS log | The client sends no `Authorization` header. |
230
+ | Response hangs, then arrives all at once | `proxy_buffering` is still on (stage 2 only). |
231
+ | Two instances, one won't start | Same `MCP_HTTP_PORT` in both env files. `journalctl` shows the bind error. |
232
+ | Every tool 403 `KEY_MISMATCH` while `health` works | Infobase key does not match the publication — a BAS-side issue; startup logs a warning for it. |
233
+ | `404` on every tool, `health` fine | `ONEC_BASE_URL` still carries the `/ai/v1` suffix. |
234
+ | `No matching distribution found for httpx` (or mcp/starlette/uvicorn) | The TestPyPI install is missing `--extra-index-url https://pypi.org/simple/`. |
235
+ | `mcbp-http: command not found` | Version 0.1.0 got installed — it predates the HTTP transport. Pin `==0.2.0`. |
236
+ | Edits to the checkout have no effect | Fallback install only: `mcbp-core` went in without `-e`. |
@@ -0,0 +1,62 @@
1
+ # Підключення до MCP-сервера mcbp-ai (Windows)
2
+
3
+ Комплект для машини **клієнта**. Сервер уже піднятий кимось іншим — тут лише три кроки.
4
+
5
+ Що вам мають видати:
6
+
7
+ | Що | Приклад | Звідки |
8
+ |---|---|---|
9
+ | Адреса сервера | `https://<адреса сервера>:8443/mcp` | адміністратор сервера |
10
+ | Токен доступу | довгий випадковий рядок | адміністратор (`new-token.cmd` на сервері) |
11
+ | Файл сертифіката | `mcbp-mcp.cer` | адміністратор — лише якщо адреса на `https` |
12
+
13
+ Токен — це ваш пароль до BAS: він зіставлений з конкретною обліковкою, і всі дії
14
+ в журналі реєстрації бази підуть від вашого імені. Не пересилайте його далі.
15
+
16
+ ## Крок 1. Довіра до сертифіката (лише для `https`)
17
+
18
+ Покладіть `mcbp-mcp.cer` поруч із `trust-cert.cmd`, натисніть на скрипті правою
19
+ кнопкою → **Запуск від імені адміністратора**. Без прав адміністратора скрипт
20
+ одразу скаже про це і нічого не змінить.
21
+
22
+ Скрипт вносить сертифікат у сховище «Довірені кореневі центри сертифікації».
23
+ Якщо адреса сервера на `http://` — цей крок пропускається.
24
+
25
+ ## Крок 2. Реєстрація сервера в Codex
26
+
27
+ Запустіть `setup-codex.cmd`. Він спитає адресу та токен (або візьміть їх
28
+ аргументами: `setup-codex.cmd https://<адреса сервера>:8443/mcp <токен>`).
29
+
30
+ Скрипт:
31
+
32
+ 1. знаходить `codex.exe` у `PATH`, а якщо там немає — у
33
+ `%LOCALAPPDATA%\OpenAI\Codex\bin\`;
34
+ 2. зберігає токен у змінній оточення користувача `MCBP_TOKEN` (`setx`);
35
+ 3. виконує `codex mcp add mcbp --url <адреса> --bearer-token-env-var MCBP_TOKEN`.
36
+
37
+ **Після цього закрийте вікно і відкрийте нове** — `setx` діє лише на нові процеси.
38
+
39
+ ## Крок 3. Перевірка
40
+
41
+ ```
42
+ codex mcp list
43
+ ```
44
+
45
+ Сервер `mcbp` має бути у списку. Далі в Codex попросіть, наприклад, показати
46
+ перелік довідників — має відповісти база, а не помилка авторизації.
47
+
48
+ ## Якщо не працює
49
+
50
+ | Симптом | Причина |
51
+ |---|---|
52
+ | `400 BAD_AUTHORIZATION` | токен не той або застарів — запитайте новий |
53
+ | помилка сертифіката / TLS | не виконано крок 1, або сертифікат виданий на іншу адресу |
54
+ | `codex.exe not found` | не встановлено Codex CLI |
55
+ | працювало, перестало після зміни токена | не перевідкрито термінал після `setx` |
56
+ | з'єднання не встановлюється | сервер вимкнено або закрито порт — до адміністратора |
57
+
58
+ ## Claude Code замість Codex
59
+
60
+ Claude Code вміє довільні заголовки, тож токен передається так само —
61
+ заголовком `Authorization: Bearer <токен>`. Схема `Basic` з обліковкою BAS
62
+ теж і далі приймається.