@oneentry/mcp-platform-server 0.1.0 → 0.1.3

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 (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +135 -92
  3. package/data/permissions.json +411 -0
  4. package/dist/api/audit.d.ts +0 -11
  5. package/dist/api/audit.js +0 -10
  6. package/dist/api/auth.d.ts +0 -18
  7. package/dist/api/auth.js +0 -20
  8. package/dist/api/build-catalog.d.ts +10 -0
  9. package/dist/api/build-catalog.js +187 -0
  10. package/dist/api/catalog.d.ts +8 -15
  11. package/dist/api/catalog.js +54 -16
  12. package/dist/api/client.d.ts +0 -24
  13. package/dist/api/client.js +6 -28
  14. package/dist/api/normalize-schema.d.ts +3 -0
  15. package/dist/api/normalize-schema.js +133 -0
  16. package/dist/api/policy.d.ts +0 -23
  17. package/dist/api/policy.js +0 -0
  18. package/dist/api/shape.d.ts +0 -13
  19. package/dist/api/shape.js +0 -21
  20. package/dist/api/swagger-source.d.ts +11 -0
  21. package/dist/api/swagger-source.js +58 -0
  22. package/dist/api/types.d.ts +0 -34
  23. package/dist/api/types.js +0 -1
  24. package/dist/bin/cli.d.ts +0 -1
  25. package/dist/bin/cli.js +11 -7
  26. package/dist/config/config.d.ts +66 -46
  27. package/dist/config/config.js +47 -44
  28. package/dist/index.d.ts +9 -7
  29. package/dist/index.js +5 -6
  30. package/dist/knowledge/chunk.d.ts +0 -15
  31. package/dist/knowledge/chunk.js +0 -16
  32. package/dist/knowledge/github.d.ts +21 -0
  33. package/dist/knowledge/github.js +174 -0
  34. package/dist/knowledge/loader.d.ts +23 -24
  35. package/dist/knowledge/loader.js +69 -108
  36. package/dist/knowledge/search.d.ts +1 -12
  37. package/dist/knowledge/search.js +19 -20
  38. package/dist/knowledge/tar.d.ts +7 -0
  39. package/dist/knowledge/tar.js +87 -0
  40. package/dist/knowledge/types.d.ts +8 -22
  41. package/dist/knowledge/types.js +0 -1
  42. package/dist/server.d.ts +1 -12
  43. package/dist/server.js +21 -18
  44. package/dist/session.d.ts +1 -18
  45. package/dist/session.js +3 -13
  46. package/dist/tools/api-call.d.ts +0 -2
  47. package/dist/tools/api-call.js +0 -23
  48. package/dist/tools/api-discovery.d.ts +0 -2
  49. package/dist/tools/api-discovery.js +2 -5
  50. package/dist/tools/docs.d.ts +0 -2
  51. package/dist/tools/docs.js +1 -5
  52. package/dist/tools/guide.d.ts +0 -7
  53. package/dist/tools/guide.js +11 -11
  54. package/dist/tools/result.d.ts +0 -8
  55. package/dist/tools/result.js +0 -7
  56. package/dist/tools/whoami.d.ts +0 -2
  57. package/dist/tools/whoami.js +5 -3
  58. package/dist/transports/http.d.ts +0 -6
  59. package/dist/transports/http.js +17 -18
  60. package/dist/transports/stdio.d.ts +0 -6
  61. package/dist/transports/stdio.js +1 -7
  62. package/knowledge/operating-rules.md +121 -0
  63. package/package.json +20 -7
  64. package/data/catalog.json +0 -1
  65. package/data/knowledge.json +0 -1
  66. package/dist/api/audit.d.ts.map +0 -1
  67. package/dist/api/audit.js.map +0 -1
  68. package/dist/api/auth.d.ts.map +0 -1
  69. package/dist/api/auth.js.map +0 -1
  70. package/dist/api/catalog.d.ts.map +0 -1
  71. package/dist/api/catalog.js.map +0 -1
  72. package/dist/api/client.d.ts.map +0 -1
  73. package/dist/api/client.js.map +0 -1
  74. package/dist/api/policy.d.ts.map +0 -1
  75. package/dist/api/policy.js.map +0 -1
  76. package/dist/api/shape.d.ts.map +0 -1
  77. package/dist/api/shape.js.map +0 -1
  78. package/dist/api/types.d.ts.map +0 -1
  79. package/dist/api/types.js.map +0 -1
  80. package/dist/bin/cli.d.ts.map +0 -1
  81. package/dist/bin/cli.js.map +0 -1
  82. package/dist/config/config.d.ts.map +0 -1
  83. package/dist/config/config.js.map +0 -1
  84. package/dist/index.d.ts.map +0 -1
  85. package/dist/index.js.map +0 -1
  86. package/dist/knowledge/chunk.d.ts.map +0 -1
  87. package/dist/knowledge/chunk.js.map +0 -1
  88. package/dist/knowledge/loader.d.ts.map +0 -1
  89. package/dist/knowledge/loader.js.map +0 -1
  90. package/dist/knowledge/search.d.ts.map +0 -1
  91. package/dist/knowledge/search.js.map +0 -1
  92. package/dist/knowledge/types.d.ts.map +0 -1
  93. package/dist/knowledge/types.js.map +0 -1
  94. package/dist/server.d.ts.map +0 -1
  95. package/dist/server.js.map +0 -1
  96. package/dist/session.d.ts.map +0 -1
  97. package/dist/session.js.map +0 -1
  98. package/dist/tools/api-call.d.ts.map +0 -1
  99. package/dist/tools/api-call.js.map +0 -1
  100. package/dist/tools/api-discovery.d.ts.map +0 -1
  101. package/dist/tools/api-discovery.js.map +0 -1
  102. package/dist/tools/docs.d.ts.map +0 -1
  103. package/dist/tools/docs.js.map +0 -1
  104. package/dist/tools/guide.d.ts.map +0 -1
  105. package/dist/tools/guide.js.map +0 -1
  106. package/dist/tools/result.d.ts.map +0 -1
  107. package/dist/tools/result.js.map +0 -1
  108. package/dist/tools/whoami.d.ts.map +0 -1
  109. package/dist/tools/whoami.js.map +0 -1
  110. package/dist/transports/http.d.ts.map +0 -1
  111. package/dist/transports/http.js.map +0 -1
  112. package/dist/transports/stdio.d.ts.map +0 -1
  113. package/dist/transports/stdio.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OneEntry
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.
package/README.md CHANGED
@@ -1,16 +1,23 @@
1
1
  # @oneentry/mcp-platform-server
2
2
 
3
- MCP server that lets an AI agent **operate the OneEntry Admin API** — grounded in the project's own
4
- rules rather than in guesswork.
3
+ MCP server that lets an AI agent **operate the OneEntry Admin API** — grounded in published
4
+ operating rules rather than in guesswork.
5
5
 
6
- Two things are bundled:
6
+ Nothing is frozen into the package. Both halves are fetched at runtime and cached:
7
7
 
8
- - **Knowledge.** ~1 MB of reference docs from `backend-app/.claude/**` and `frontend-app/.claude/**`,
9
- split into ~1700 searchable sections, plus a hand-written `operating-rules.md` distilling the
10
- payload rules that actually break calls.
11
- - **Actuation.** All **478 Admin API operations** as a searchable catalog with parameter and body
12
- schemas, the permission each one requires, and a risk classification reachable through one
13
- generic invoke tool instead of 478 separate ones.
8
+ - **Knowledge.** Small markdown files from a public GitHub repository
9
+ ([ONEENTRY-PLATFORM/oneentry-platform-rules](https://github.com/ONEENTRY-PLATFORM/oneentry-platform-rules)),
10
+ chunked into searchable sections. Editing a doc there — through the GitHub UI, a PR, whatever — takes effect on the next
11
+ restart of any server, with no npm release.
12
+ - **Actuation.** Every Admin API operation, built from the instance's own API document at
13
+ `GET /api/admin/workflows/nodes/admin-api/swagger.json`, with parameter and body schemas, the
14
+ permission each one requires, and a risk classification — reachable through one generic invoke
15
+ tool instead of one tool per endpoint. The catalog always describes the API that is actually
16
+ running, not a snapshot of it.
17
+
18
+ The one thing still committed here is `data/permissions.json`: the permission an operation requires
19
+ is declared in platform code and never reaches the API document, so that map has to be recovered
20
+ separately.
14
21
 
15
22
  Only the Admin API is exposed.
16
23
 
@@ -20,7 +27,7 @@ Only the Admin API is exposed.
20
27
  npm i -g @oneentry/mcp-platform-server # or use npx, below
21
28
  ```
22
29
 
23
- ## Local mode (agent and CMS on the same machine)
30
+ ## Local mode (agent and instance on the same machine)
24
31
 
25
32
  Add to `.mcp.json` in the repo you work from:
26
33
 
@@ -29,7 +36,7 @@ Add to `.mcp.json` in the repo you work from:
29
36
  "mcpServers": {
30
37
  "oneentry-mcp-platform": {
31
38
  "command": "npx",
32
- "args": ["-y", "@oneentry/mcp-platform-server", "--back", "../backend-app", "--front", "../frontend-app"],
39
+ "args": ["-y", "@oneentry/mcp-platform-server"],
33
40
  "env": {
34
41
  "ONEENTRY_CMS_BASE_URL": "http://localhost:3003/api/admin",
35
42
  "ONEENTRY_CMS_LOGIN": "your-admin-login",
@@ -40,20 +47,21 @@ Add to `.mcp.json` in the repo you work from:
40
47
  }
41
48
  ```
42
49
 
43
- `--back`/`--front` are optional: with them the knowledge is read live from the checkouts, so edits
44
- to `.claude/docs` take effect immediately; without them the bundled snapshot is used.
50
+ Nothing has to be checked out locally. On first run the knowledge repository is downloaded once and
51
+ cached; later runs re-use the cache and only ask GitHub whether the commit moved, at most once an
52
+ hour.
45
53
 
46
- backend-app must be running with `API_TYPE=/api/admin` (default port 3003).
54
+ The base URL must point at an instance that exposes the Admin API under `/api/admin`.
47
55
 
48
- ## Remote mode (hosted, many agents, one stand)
56
+ ## Remote mode (hosted, many agents, one instance)
49
57
 
50
58
  ```bash
51
59
  oneentry-mcp-platform --http --port 8931 --audit /var/log/oneentry-mcp-platform-audit.jsonl \
52
- --base-url https://stand.example/api/admin \
60
+ --base-url https://your-instance.example/api/admin \
53
61
  --allowed-origins https://agent.example
54
62
  ```
55
63
 
56
- Each MCP session carries its own CMS identity, passed as connection headers — never as tool
64
+ Each MCP session carries its own identity, passed as connection headers — never as tool
57
65
  arguments, so a prompt-injected instruction cannot swap identities:
58
66
 
59
67
  | header | meaning |
@@ -63,43 +71,6 @@ arguments, so a prompt-injected instruction cannot swap identities:
63
71
 
64
72
  Sessions never share tokens or confirm tokens. `GET /health` reports liveness and session count.
65
73
 
66
- ## Docker
67
-
68
- ```bash
69
- cp .env.example .env # fill in ONEENTRY_CMS_LOGIN / ONEENTRY_CMS_PASSWORD
70
- docker compose up --build mcp # remote mode on :8931
71
- curl -s http://127.0.0.1:8931/health # {"ok":true,"mode":"remote","sessions":0}
72
- ```
73
-
74
- | service | profile | mode |
75
- |---|---|---|
76
- | `mcp` | — | remote (Streamable HTTP) on `:8931` |
77
- | `mcp-live` | `live` | remote, knowledge read live from the mounted checkouts |
78
- | `stdio` | `stdio` | local stdio — usable directly as an MCP client command |
79
-
80
- ```bash
81
- docker compose --profile live up --build mcp-live
82
- docker compose --profile stdio run --rm -T stdio
83
- ```
84
-
85
- A stand running on the host is reachable as `host.docker.internal` (wired via `host-gateway`), so
86
- the default base URL inside the container is `http://host.docker.internal:3003/api/admin`. The audit
87
- log is bind-mounted to `./.audit`. Details and gotchas: [.claude/docs/docker.md](.claude/docs/docker.md).
88
-
89
- To point an MCP client at the containerised stdio server:
90
-
91
- ```json
92
- {
93
- "mcpServers": {
94
- "oneentry-mcp-platform": {
95
- "command": "docker",
96
- "args": ["compose", "-f", "/abs/path/to/docker-compose.yml", "--profile", "stdio",
97
- "run", "--rm", "-T", "stdio"]
98
- }
99
- }
100
- }
101
- ```
102
-
103
74
  ## The seven tools
104
75
 
105
76
  | tool | what it does |
@@ -108,9 +79,9 @@ To point an MCP client at the containerised stdio server:
108
79
  | `cms_docs_search` | search the knowledge base, returns sections with snippets |
109
80
  | `cms_docs_read` | read one section, with its sibling sections listed for paging |
110
81
  | `cms_api_search` | find operations by keyword / tag / method / mutating |
111
- | `cms_api_describe` | params, body schema, required permission, risk, linked docs |
82
+ | `cms_api_describe` | params, body schema, required permission, risk, confirm gating |
112
83
  | `cms_api_call` | execute one operation; `dryRun` and confirm-gating for mutations |
113
- | `cms_whoami` | mode, base URL, admin + permissions, catalog/knowledge versions and warnings |
84
+ | `cms_whoami` | mode, base URL, admin + permissions, knowledge commit, catalog state and warnings |
114
85
 
115
86
  Plus two MCP resources: `oneentry://knowledge/mcp/operating-rules` and `oneentry://knowledge/index`.
116
87
 
@@ -130,60 +101,132 @@ Read-only by default. `--allow` (or `ONEENTRY_MCP_ALLOW`) raises it:
130
101
  `auth/logout/all-users`), require a **confirm token**: the first call returns the token *and the
131
102
  current state of the target*, fetched via the sibling GET, so a human can see what is about to
132
103
  change. The token is bound to `hash(opId + arguments)`, single-use, valid 5 minutes.
133
- - Permissions are checked locally first: if the admin lacks the `AdminPermissionsEnum` value the
134
- operation declares, the call is refused without being sent.
104
+ - Permissions are checked locally first: if the admin lacks the permission the operation declares,
105
+ the call is refused without being sent.
135
106
  - Every non-GET call is appended to a JSONL audit log (arguments are hashed, not stored). Required
136
107
  in remote mode.
137
108
 
138
- ## Keeping the bundled data in sync
109
+ ## Where the knowledge comes from
110
+
111
+ A public GitHub repository of small markdown files. Layout:
139
112
 
140
- `data/catalog.json` and `data/knowledge.json` are committed build outputs:
113
+ ```
114
+ knowledge/
115
+ mcp/operating-rules.md -> docId "mcp/operating-rules"
116
+ mcp/docs/api/orders.md -> docId "mcp/docs/api/orders"
117
+ mcp/docs/server/cms-api-call.md -> docId "mcp/docs/server/cms-api-call"
118
+ mcp/docs/api/orders/index.md -> docId "mcp/docs/api/orders"
119
+ ```
120
+
121
+ The docId is the path under `knowledge/` without `.md`; `<name>/index.md` collapses back to
122
+ `<name>`, which is what lets a long document be split into a directory of small files without
123
+ changing the id anything already links to. **No file over 12 KB** — the same cap `cms_docs_read`
124
+ applies to a section, so a section can never come back truncated.
125
+
126
+ At startup the server asks GitHub for the commit behind `--knowledge-ref` (one cheap request,
127
+ skipped entirely while the cache is younger than `--knowledge-ttl`, default 1 h), and downloads
128
+ `codeload.github.com/<repo>/tar.gz/<sha>` only when that commit has moved — one request for the
129
+ whole corpus, no API rate limit. The tree is unpacked into `--cache-dir` keyed by commit, so a
130
+ restart is instant and the previous commit is pruned.
131
+
132
+ | situation | what happens |
133
+ |---|---|
134
+ | cache fresh | no network at all |
135
+ | commit unchanged | one API request, no download |
136
+ | commit moved | one API request + one archive download |
137
+ | GitHub unreachable, cache present | the cached commit is used |
138
+ | GitHub unreachable, no cache | falls back to the bundled `operating-rules.md`, and says so in `cms_guide` and `cms_whoami` |
139
+ | `--offline` | cache and bundled rules only, never the network |
140
+
141
+ Which repository and which commit is a configuration choice, not a constant: `--knowledge-repo`
142
+ (`ONEENTRY_MCP_KNOWLEDGE_REPO`, default `ONEENTRY-PLATFORM/oneentry-platform-rules`) and
143
+ `--knowledge-ref`
144
+ (`ONEENTRY_MCP_KNOWLEDGE_REF`, default `main`) — a ref may be a branch, a tag or a commit sha, so an
145
+ instance can be frozen on a known-good corpus. `ONEENTRY_GITHUB_TOKEN` is optional and only raises
146
+ the API rate limit; the repo is public. `cms_whoami` reports the repo, ref and 12-character commit
147
+ the running server actually loaded.
148
+
149
+ ### Reading the docs from a local folder instead
150
+
151
+ For testing, or for editing docs without committing, point the server at a directory:
141
152
 
142
153
  ```bash
143
- npm run sync:from-cms -- --back ../backend-app --front ../frontend-app
144
- npm run check:drift # CI: fails if the committed data is stale
154
+ ONEENTRY_MCP_KNOWLEDGE_PATH=../oneentry-platform-rules oneentry-mcp-platform
155
+ # or: oneentry-mcp-platform --knowledge-path ../oneentry-platform-rules
145
156
  ```
146
157
 
147
- Both commands fall back to sibling checkouts (`../dnk-back`, `../dnk-front`) when the flags are
148
- omitted, so the bare form works in the usual layout. `check:drift` runs automatically as
149
- `prestart`, i.e. before `npm start` it compares content only, and **skips** (exit 0) when no
150
- checkout is next to the repo, so a deployment without the CMS sources still starts. It does not
151
- run before `npm run dev`: the ts-morph pass over every controller takes ~20 s, which is too slow
152
- for a restart loop.
158
+ Either the clone root or its `knowledge/` directory works the path is probed for a `knowledge`
159
+ subdirectory and used directly if there is none. While it is set, GitHub is never contacted and the
160
+ cache is not consulted, so edits take effect on the next restart with no commit and no push;
161
+ `cms_whoami` reports `source: "local"`. Leave it unset for the default, which is GitHub.
162
+
163
+ A path that cannot be read is a **startup error**, not a fallback: unlike GitHub being unreachable,
164
+ a bad path is a configuration mistake, and degrading silently to the bundled operating rules would
165
+ look like an empty corpus.
166
+
167
+ ### Known limitation: retrieval is single-language
153
168
 
154
- Refreshing `swagger.json` itself is dnk-back's job boot it once with `API_TYPE=/api/admin` and it
155
- rewrites `local-static/swagger.json` on startup.
169
+ Search does not translate, and the corpus is English. A question asked in another language will not
170
+ find an English-only document no matter how it is weighted. The fix is content, not code.
156
171
 
157
- The catalog is built from two sources, because neither alone is enough:
172
+ ### The corpus is hand-authored
158
173
 
159
- 1. `backend-app/local-static/swagger.json` paths, operation ids and schemas.
160
- 2. `backend-app/src/**/*.controller.ts` via ts-morph the `@GrantByPermission` decorators, which
161
- never reach swagger. Base classes are walked, since Nest derives the operation id from the
162
- registered controller while the handler often lives in a parent.
174
+ The knowledge repository is written for a public audience, one file at a time. There is no import
175
+ script and one must not be added: bulk-copying internal documentation into it would publish source
176
+ paths, storage identifiers and tracker ids in a single command, and a repository that is public and
177
+ mirrored cannot be un-published. `npm run publish:knowledge` exists only to refuse.
163
178
 
164
- Two classes of inconsistency in dnk-back are surfaced as catalog `warnings` (visible in
165
- `cms_whoami`) rather than silently swallowed:
179
+ At release time, copy `knowledge/mcp/operating-rules.md` from the rules repository over this
180
+ package's `knowledge/operating-rules.md` the rules repository is the source, and the bundled seed
181
+ is never hand-edited.
166
182
 
167
- - handlers that declare a permission but are missing from `swagger.json` — a stale swagger snapshot;
168
- - permissions required by controllers but absent from `AdminPermissionsEnum` (`users.export`,
169
- `orders.export`, `payments.export`) — no admin can hold these, so those endpoints always 403.
183
+ ## Where the catalog comes from
170
184
 
171
- ### The swagger is not valid JSON Schema
185
+ Two sources, because neither alone is enough:
172
186
 
173
- 311 `type` values in the source swagger are TypeScript expressions (`Record<string, any>`,
174
- `CommonLocalizeInfos`, `AttributesSets`, `image`, `date`). The build normalizes what it can and
175
- marks the rest `"x-loose": true` with the original under `x-source-type`. For loose fields the
176
- `example` is the contract, and client-side body validation is deliberately advisory the server
177
- is the real validator, so a call is never blocked because a loose field could not be checked.
187
+ 1. `GET {baseUrl}/workflows/nodes/admin-api/swagger.json?langCode=en_US` paths, operation ids and
188
+ schemas, straight from the instance you are pointed at. Cached per base URL, so an unreachable
189
+ instance still yields the last known catalog.
190
+ 2. `data/permissions.json` the permission each operation requires, which the API document does not
191
+ express. Regenerate it when the platform changes its permissions:
192
+
193
+ ```bash
194
+ npm run sync:permissions -- --back <path to a platform checkout>
195
+ ```
196
+
197
+ That pass walks base classes too, since the operation id is derived from the *registered* class
198
+ while the handler often lives in a parent.
199
+
200
+ Inconsistencies are surfaced as catalog `warnings` (visible in `cms_whoami`) rather than swallowed:
201
+ paths outside `/api/admin` (dropped — this server exposes the Admin API only), operations with no
202
+ `operationId`, operations whose permission is known but which the instance does not expose, and
203
+ permissions that operations require but no admin can hold — such endpoints answer 403 forever.
204
+
205
+ If the instance does not serve its API document at all, the catalog is **empty** and says so loudly.
206
+ Empty is safe — there is nothing to call — but silence would read as "the platform has no such
207
+ endpoint".
208
+
209
+ ### The API document is not valid JSON Schema
210
+
211
+ Many `type` values in the source document are expressions in the platform's own type language
212
+ (`Record<string, any>`, `CommonLocalizeInfos`, `AttributesSets`, `image`, `date`). The build
213
+ normalizes what it can and marks the rest `"x-loose": true` with the original under `x-source-type`.
214
+ For loose fields the `example` is the contract, and client-side body validation is deliberately
215
+ advisory — the instance is the real validator, so a call is never blocked because a loose field
216
+ could not be checked.
178
217
 
179
218
  ## Tests
180
219
 
181
220
  ```bash
182
- npm test # unit + MCP protocol + remote-mode transport (no stand needed)
183
- npm run test:integration # against a live dnk-back; skips cleanly if unreachable
221
+ npm test # unit + MCP protocol + remote-mode transport (no instance needed)
222
+ npm run test:integration # against a live instance; skips cleanly if unreachable
184
223
  ```
185
224
 
186
- The integration suite needs backend-app on `:3003` with `API_TYPE=/api/admin` plus
187
- `ONEENTRY_CMS_LOGIN`/`ONEENTRY_CMS_PASSWORD`. It walks search → describe → call, asserts a read-only refusal
188
- sends nothing, and runs a full create → confirm-gated delete cycle on a throwaway template while
189
- checking the audit log.
225
+ The integration suite needs `ONEENTRY_CMS_BASE_URL` pointing at an instance that exposes the Admin
226
+ API, plus `ONEENTRY_CMS_LOGIN`/`ONEENTRY_CMS_PASSWORD`. It walks search → describe → call, asserts a
227
+ read-only refusal sends nothing, and runs a full create → confirm-gated delete cycle on a throwaway
228
+ template while checking the audit log.
229
+
230
+ ## License
231
+
232
+ MIT — see [LICENSE](LICENSE).