iit-mcp-hudu 0.2.3 → 0.3.0
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.
- package/.env.example +13 -42
- package/CHANGELOG.md +31 -0
- package/README.md +92 -56
- package/dist/auth.js +0 -1
- package/dist/base-hosts.js +0 -1
- package/dist/config.js +24 -44
- package/dist/credential.js +0 -1
- package/dist/doctor.js +0 -1
- package/dist/http.js +0 -1
- package/dist/lifecycle.js +0 -1
- package/dist/logger.js +0 -1
- package/dist/main.js +23 -1
- package/dist/policy.js +59 -9
- package/dist/stdio.js +0 -1
- package/dist/tools/article-loss.js +61 -0
- package/dist/tools/company-context-policy.js +0 -1
- package/dist/tools/context.js +0 -1
- package/dist/tools/dedicated.js +55 -79
- package/dist/tools/dispatch.js +25 -13
- package/dist/tools/fetch-many.js +3 -4
- package/dist/tools/lists.js +117 -160
- package/dist/tools/meta.js +3 -6
- package/dist/tools/reads.js +37 -36
- package/dist/tools/register.js +0 -1
- package/dist/tools/renamed.js +13 -0
- package/dist/tools/result-urls.js +0 -1
- package/dist/tools/results.js +7 -1
- package/dist/tools/schemas.js +5 -3
- package/dist/tools/search-policy.js +0 -1
- package/dist/tools/search-prose.js +5 -3
- package/dist/tools/search.js +0 -2
- package/dist/tools/server.js +25 -24
- package/dist/tools/slim.js +34 -0
- package/dist/tools.js +1 -2
- package/package.json +9 -10
- package/dist/auth.d.ts +0 -51
- package/dist/base-hosts.d.ts +0 -20
- package/dist/config.d.ts +0 -95
- package/dist/credential.d.ts +0 -67
- package/dist/doctor.d.ts +0 -64
- package/dist/http.d.ts +0 -60
- package/dist/lifecycle.d.ts +0 -29
- package/dist/logger.d.ts +0 -24
- package/dist/main.d.ts +0 -2
- package/dist/policy.d.ts +0 -79
- package/dist/stdio.d.ts +0 -21
- package/dist/tools/company-context-policy.d.ts +0 -16
- package/dist/tools/context.d.ts +0 -31
- package/dist/tools/dedicated.d.ts +0 -123
- package/dist/tools/dispatch.d.ts +0 -4
- package/dist/tools/fetch-many.d.ts +0 -42
- package/dist/tools/lists.d.ts +0 -49
- package/dist/tools/meta.d.ts +0 -7
- package/dist/tools/reads.d.ts +0 -7
- package/dist/tools/register.d.ts +0 -13
- package/dist/tools/result-urls.d.ts +0 -2
- package/dist/tools/results.d.ts +0 -180
- package/dist/tools/schemas.d.ts +0 -119
- package/dist/tools/search-policy.d.ts +0 -165
- package/dist/tools/search-prose.d.ts +0 -79
- package/dist/tools/search.d.ts +0 -6
- package/dist/tools/server.d.ts +0 -77
- package/dist/tools.d.ts +0 -34
package/.env.example
CHANGED
|
@@ -57,27 +57,21 @@ MCP_TRANSPORT=stdio
|
|
|
57
57
|
# caller that can reach the listener is trusted.
|
|
58
58
|
# HUDU_ALLOW_ANY_BASE_HOST=false
|
|
59
59
|
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# Every write and destructive operation is permitted; the SDK's per-mode
|
|
70
|
-
# client gates (read / write / destructive) still apply underneath.
|
|
71
|
-
HUDU_READ_ONLY=true
|
|
72
|
-
# HUDU_READ_ONLY=false
|
|
73
|
-
|
|
74
|
-
# Authority over the mutation dispatchers. deny (the default) refuses every write and destructive
|
|
75
|
-
# operation; allow_list permits only the keys in HUDU_WRITE_ALLOW; all is today's behaviour and has
|
|
76
|
-
# to be typed out deliberately. An unset value means deny, so forgetting this gets a read-only
|
|
77
|
-
# server, never an open one. Reads are never affected.
|
|
60
|
+
# Deployment spectrum -- HUDU_WRITE_POLICY is the one switch for mutation, and every step is fail-closed:
|
|
61
|
+
# read-only : HUDU_WRITE_POLICY=deny (default). No hudu_write / hudu_delete tool exists.
|
|
62
|
+
# scoped writes : HUDU_WRITE_POLICY=allow_list + HUDU_WRITE_ALLOW=<operation keys>. hudu_write is
|
|
63
|
+
# registered only if the list has a write op, hudu_delete only if it has a destructive op.
|
|
64
|
+
# full read-write : HUDU_WRITE_POLICY=all. The SDK's per-mode client gates still apply underneath.
|
|
65
|
+
# Plaintext credential reads are a separate switch: HUDU_SECRET_READS=allow.
|
|
66
|
+
|
|
67
|
+
# Authority over the mutation dispatchers. deny (the default) registers no mutation tool; allow_list permits only the keys in HUDU_WRITE_ALLOW; all is today's behaviour and has
|
|
68
|
+
# to be typed out deliberately. Unset means deny. Reads are never affected.
|
|
78
69
|
HUDU_WRITE_POLICY=deny
|
|
79
70
|
# HUDU_WRITE_POLICY=allow_list
|
|
80
71
|
# HUDU_WRITE_POLICY=all
|
|
72
|
+
# Hard override: true means no write/delete tool and every mutation refused, whatever the policy and
|
|
73
|
+
# allow-list say. Unset or false leaves HUDU_WRITE_POLICY in charge.
|
|
74
|
+
# HUDU_READ_ONLY=true
|
|
81
75
|
|
|
82
76
|
# Authority over reads that return a plaintext credential -- independent of HUDU_WRITE_POLICY, which
|
|
83
77
|
# never covers them: asset_passwords.get returns password and otp_secret as an ordinary read. deny
|
|
@@ -91,6 +85,7 @@ HUDU_SECRET_READS=deny
|
|
|
91
85
|
# Only read under HUDU_WRITE_POLICY=allow_list. Comma-separated registry operation keys, e.g.
|
|
92
86
|
# articles.update,assets.create. It must be non-empty and every key must exist in the registry (and
|
|
93
87
|
# be a write) or the container fails at boot with the variable named. Ignored under deny/all.
|
|
88
|
+
# Allowing a write also allows the writes it delegates to (articles.patchSection -> articles.update).
|
|
94
89
|
# The write client is offered the write-effect keys and the delete client the destructive-effect
|
|
95
90
|
# keys from this one list: to reach the same coverage as `all` via an allow list, it must name
|
|
96
91
|
# every write and every destructive operation (the full key set is the node-hudu capability
|
|
@@ -101,30 +96,6 @@ HUDU_SECRET_READS=deny
|
|
|
101
96
|
PORT=8787
|
|
102
97
|
LOG_LEVEL=info
|
|
103
98
|
|
|
104
|
-
# Pages the search body index walks per resource (SDK default 250 = 25,000 records). Lower it when
|
|
105
|
-
# the tenant's assets are numerous enough to evict article bodies from the 64 MB text budget: the
|
|
106
|
-
# article walk must fit with room to spare or every search answers body-blind. 40 covers ~4,000
|
|
107
|
-
# articles and caps assets at ~4,000.
|
|
108
|
-
HUDU_SEARCH_MAX_INDEX_PAGES=40
|
|
109
|
-
|
|
110
|
-
# The read-through cache the SDK clients use (SDK 0.12.0). on (the default) selects the SDK's
|
|
111
|
-
# per-entity TTL preset (1 h structural / 5 min mutable / 1 min base): repeated reads of the
|
|
112
|
-
# same record are served from the cache until the TTL expires. off disables it: every read
|
|
113
|
-
# re-dials Hudu.
|
|
114
|
-
# HUDU_CACHE_PRESET=on
|
|
115
|
-
|
|
116
|
-
# The 429 cooldown gate (SDK 0.12.0). on (the default) arms a local deadline after a vendor
|
|
117
|
-
# 429 whose retries are spent (the profile's 1 retry: the first such call dials twice): calls
|
|
118
|
-
# the gate refuses are answered locally with notSent: true, never re-dialed. off
|
|
119
|
-
# retries a 429 per the agent profile's retry budget instead of arming a local deadline.
|
|
120
|
-
# HUDU_COOLDOWN=on
|
|
121
|
-
|
|
122
|
-
# The whole-call deadline in milliseconds (SDK 0.12.0). Must be a positive integer. Defaults
|
|
123
|
-
# to 30000 and is always sent to the SDK explicitly, so it stays above the agent profile's
|
|
124
|
-
# 20 s deadline regardless of preset: the default keeps the tolerance a cold tier:'index'
|
|
125
|
-
# body-index walk had before the 0.12.0 adoption.
|
|
126
|
-
# HUDU_TIMEOUT_MS=30000
|
|
127
|
-
|
|
128
99
|
# The resources hudu_search and hudu_resolve_any may offer and query. Unset means the SDK's full
|
|
129
100
|
# searchable set (articles, asset_passwords, assets, companies, groups, password_folders, users,
|
|
130
101
|
# websites). Set it to the subset THIS deployment's Hudu API key can read, so a tool neither
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
**Upgrading from 0.2.3: breaking changes.** (1) Article bodies through `hudu_read` / `hudu_write` / `hudu_delete` are Markdown by default. (2) `HUDU_CACHE_PRESET`, `HUDU_COOLDOWN`, `HUDU_TIMEOUT_MS` and `HUDU_SEARCH_MAX_INDEX_PAGES` are removed and a set one now fails boot. (3) `hudu_list_companies` / `_articles` / `_assets` / `_asset_layouts` are replaced by `hudu_list(resource)`. (4) Served tool definitions drop `_meta`. (5) The repository uses pnpm. The SDK is pinned exactly to node-hudu 0.16.0 (plan hash `fba0aa46...`); 0.15.0 was never published. `HUDU_READ_ONLY` is NOT removed: `true` is a hard override (see below). **UPGRADE HAZARD:** on 0.2.3 an UNSET `HUDU_READ_ONLY` meant read-only. Now only `HUDU_WRITE_POLICY` decides when it is unset, so these two configs, with `HUDU_READ_ONLY` unset, now give the model `hudu_write` / `hudu_delete`: (a) `HUDU_WRITE_POLICY=all`; (b) `HUDU_WRITE_POLICY=allow_list` with a non-empty `HUDU_WRITE_ALLOW`. Set `HUDU_READ_ONLY=true` to keep them read-only. The server logs a warning at boot in exactly this case.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
- Behaviour change: `hudu_read` / `hudu_write` / `hudu_delete` serve and accept article bodies as Markdown (node-hudu `contentFormat: 'markdown'`). Bodies that open with block HTML are still sent as HTML.
|
|
9
|
+
- Tooling: the repository moves from npm to pnpm (`pnpm-lock.yaml`, `pnpm-workspace.yaml`, `packageManager`); the Dockerfile and CI use pnpm, and the runtime image carries no package manager. Contributors run `pnpm install`.
|
|
10
|
+
- The MCP handshake reports the package version (it was hardcoded 0.1.0).
|
|
11
|
+
- CLI: `--version` and `--help` work without configuration. The package no longer ships source maps or `.d.ts` files. The SDK's tracker references ("issues #70/#72") are stripped from served tool descriptions.
|
|
12
|
+
- CI: adds `pnpm audit --prod` and a container job (build, no package manager, fail-fast boot, non-default port, origin guard).
|
|
13
|
+
- Dependency: node-hudu 0.13.2 to 0.14.0 (Hudu Markdown dialect, `articles.getSection`, `articles.patchSection`); catalog plan hash and reachable-operation counts updated for the two new operations.
|
|
14
|
+
- Dependency: node-hudu 0.14.0 -> 0.16.0 (0.15.0 was never published). New discoverable operations: `articles.pin`, `articles.unpin`, `asset_passwords.listShares` / `getShare` / `createShare` / `deleteShare`; no served tool added. `asset_layouts.create` / `update` now send the body nested under `asset_layout` (SDK fix: fields were silently dropped). Plan hash re-stamped to `fba0aa46...`.
|
|
15
|
+
- `hudu_resolve_any` returns `unauthorized` (resources the credential may not read) beside the permitted hits; it errors only when every requested resource refuses. The dispatcher path returns it too.
|
|
16
|
+
- `hudu_get_company_context` accepts `opts.skip` and returns `skipped`.
|
|
17
|
+
- Share operations: refused under the default `HUDU_SECRET_READS=deny`; `createShare` is never runnable (the SDK needs `reveal`, this server never grants it) and is listed unreachable.
|
|
18
|
+
- Fix: the `hudu_search` default-tier sentence rewrite matched stale SDK text, so `auto` and narrowed deployments advertised a false `index` default; it now fails loudly if the anchor drifts.
|
|
19
|
+
- Discovery no longer names tools this server does not serve (`hudu_get_asset_password`, `hudu_get_website`).
|
|
20
|
+
- Served surface: `$schema` and safe-integer `maximum` stripped, SDK dispatcher field text served verbatim, shorter article-format note and `hudu_search` `query` text; an intermediate 46.3 / 40.8 KB. A size-gate test ratchets both.
|
|
21
|
+
- Mermaid is stored in the bare editor-native `<pre class="language-mermaid">` form (SDK 0.16.0); round-trip test added.
|
|
22
|
+
- Breaking: the four `hudu_list_companies` / `hudu_list_articles` / `hudu_list_assets` / `hudu_list_asset_layouts` tools are replaced by one `hudu_list` with a `resource` argument. Filters move inside `filters`; an unaccepted filter is refused, `asset_layouts` refuses `page_size`. A call to an old name answers with a pointer to `hudu_list`. Same bounds and pagination as before. Resync tool catalogs.
|
|
23
|
+
- Breaking (metadata): served tool definitions no longer carry `_meta` (`backingOperation`, `sensitive`, `requiresApproval`, ...).
|
|
24
|
+
- Served surface after these: 19 tools / 39.5 KB write-enabled, 17 tools / 33.9 KB read-only (`tools/list`), down from 22 / 50.1 KB and 20 / 43.7 KB on node-hudu 0.16.0.
|
|
25
|
+
- Markdown article reads (`hudu_read` `articles.get` / `getContext` / `resolve`, `hudu_get_article_context`) that lose content the dialect cannot express (e.g. an inline `<svg>`) now set `markdown_lossy: true` and `markdown_lossy_codes` and add one sentence to the result text; a lossless read is byte-identical to before. Detection uses the SDK's `diffArticleRoundTrip` (content-impact findings only).
|
|
26
|
+
- `allow_list` registers `hudu_write` only when the list holds a write-effect operation and `hudu_delete` only when it holds a destructive-effect one (previously both). `all` registers both; `deny` and `HUDU_READ_ONLY=true` neither. The boot log states which were registered.
|
|
27
|
+
- `HUDU_WRITE_POLICY` (`deny` default | `allow_list` | `all`) is the write switch; `deny` registers no `hudu_write` / `hudu_delete` tool. Allowing a write also allows the writes it delegates to (`articles.patchSection` -> `articles.update`). `HUDU_READ_ONLY=true` stays a HARD OVERRIDE: no write or delete tool and every mutation refused, whatever `HUDU_WRITE_POLICY` / `HUDU_WRITE_ALLOW` say; unset or `false` leaves `HUDU_WRITE_POLICY` in charge. The effective policy is logged once at boot.
|
|
28
|
+
- Behaviour note: on 0.2.3 an UNSET `HUDU_READ_ONLY` defaulted to true (read-only). It is now unset = `HUDU_WRITE_POLICY` decides, so a deployment that sets `HUDU_WRITE_POLICY=allow_list` or `all` without `HUDU_READ_ONLY=true` gets the write tools. Set `HUDU_READ_ONLY=true` to keep a deployment read-only regardless.
|
|
29
|
+
- Breaking: `HUDU_CACHE_PRESET`, `HUDU_COOLDOWN`, `HUDU_TIMEOUT_MS` and `HUDU_SEARCH_MAX_INDEX_PAGES` are removed; the SDK client now uses the fixed agent profile, recommended cache, a 30 s timeout and an armed cooldown. A set one fails boot.
|
|
30
|
+
- `hudu_get_article_context` returns Markdown; `hudu_read`, `hudu_write`, `hudu_list` and `hudu_fetch_many` descriptions state which paths speak Markdown and which carry stored HTML.
|
|
31
|
+
- Tests: Markdown read and write loss-guard coverage through the dispatchers.
|
package/README.md
CHANGED
|
@@ -3,29 +3,32 @@
|
|
|
3
3
|
MCP server for Hudu IT documentation, built on the [`node-hudu`](https://www.npmjs.com/package/node-hudu) SDK.
|
|
4
4
|
|
|
5
5
|
## Tool surface
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
`
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
`
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`articles.list` / `assets.listAcrossCompanies` / `asset_layouts.list`)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
`
|
|
26
|
-
`
|
|
27
|
-
|
|
28
|
-
|
|
6
|
+
17 tools by default: `hudu_list_operations`, `hudu_describe_operation`, `hudu_read`,
|
|
7
|
+
`hudu_fetch_many`, twelve core reads, and `hudu_list`. Setting `HUDU_WRITE_POLICY` `allow_list` or
|
|
8
|
+
`all` adds `hudu_write` and `hudu_delete` (19 tools). Each dispatcher uses a separate SDK client in
|
|
9
|
+
its matching mode; names, annotations and the dispatcher field text are the SDK's curated metadata.
|
|
10
|
+
SDK enforcement rejects cross-effect calls before any request. The old `hudu_invoke`,
|
|
11
|
+
`hudu_catalog` and `hudu_describe` names are removed; consumers must resync their tool catalog.
|
|
12
|
+
|
|
13
|
+
`hudu_list(resource)` replaces the four `hudu_list_companies` / `hudu_list_articles` /
|
|
14
|
+
`hudu_list_assets` / `hudu_list_asset_layouts` tools of 0.2.x (a call to an old name answers with a
|
|
15
|
+
pointer to `hudu_list`). `resource` selects one of four fixed registry operations (`companies.list` /
|
|
16
|
+
`articles.list` / `assets.listAcrossCompanies` / `asset_layouts.list`); the tool takes exactly one
|
|
17
|
+
page and accepts no operation selector or nested invocation, so an ungated reader can page these
|
|
18
|
+
collections without the registry dispatcher and without exposing the write surface. Filters go in
|
|
19
|
+
`filters`; each resource accepts only its own set, and any other key is refused naming the accepted
|
|
20
|
+
ones (never dropped, which would turn a company-scoped request into an account-wide one). Companies,
|
|
21
|
+
articles and assets take `page` / `page_size` (capped at 100); `asset_layouts` takes `page` only,
|
|
22
|
+
because `/asset_layouts` ignores `page_size`: a caller bound would truncate the vendor page and skip
|
|
23
|
+
rows as `page` advanced, so the tool returns the vendor page whole and page N is always the
|
|
24
|
+
vendor's Nth page. For searchable resources (`companies` / `articles` / `assets`) a resource
|
|
25
|
+
excluded by `HUDU_SEARCH_RESOURCES` is refused by the list tool too, before any request;
|
|
26
|
+
`asset_layouts` is not searchable (the SDK declares no vendor text filter for it), so it follows the
|
|
27
|
+
same reachability as the existing ungated `hudu_get_asset_layout` tool. The tool set is the SDK's
|
|
28
|
+
`CORE_TOOLS` plus `hudu_list`, which is this server's own; a future SDK upgrade must not re-add the
|
|
29
|
+
four old names.
|
|
30
|
+
|
|
31
|
+
These tools run on node-hudu 0.16.0; the `SDK behaviour (0.12.0)` section below covers the behaviours that shape them: redirect refusal, the closed input contract, the error envelope, and the client opt-ins. Numeric ids must be positive integers on every tool; anything else is refused before a request is made.
|
|
29
32
|
|
|
30
33
|
`hudu_fetch_many` (node-hudu 0.12.0) fetches up to 20 single records across mixed resources in
|
|
31
34
|
one call: each item is exactly `{ resource, id, fields? }` — one of the 22 bare-id resources, a
|
|
@@ -33,7 +36,7 @@ numeric vendor id, and an optional client-side field projection. A shape violati
|
|
|
33
36
|
the schema layer before any request, and failure is per-item: a miss or a vendor fault lands on
|
|
34
37
|
its own row as a typed error while the rest of the batch is served. The deployment's secret-read
|
|
35
38
|
policy applies to the batch's items exactly as to a single read, so a credential item is
|
|
36
|
-
refused pre-request under the default `HUDU_SECRET_READS=deny`; `
|
|
39
|
+
refused pre-request under the default `HUDU_SECRET_READS=deny`; `HUDU_WRITE_POLICY=deny` keeps the tool
|
|
37
40
|
— it is a read. The same batch stays reachable through `hudu_read` with operation
|
|
38
41
|
`operations.fetchMany`, gated the same way.
|
|
39
42
|
|
|
@@ -47,7 +50,7 @@ refused pre-request under the default `HUDU_SECRET_READS=deny`; `HUDU_READ_ONLY`
|
|
|
47
50
|
## Local use (stdio)
|
|
48
51
|
|
|
49
52
|
Needs Node 24+, a Hudu API key and your Hudu origin. `stdio` is the default transport, so no
|
|
50
|
-
`MCP_TRANSPORT` is needed. Mutation tools stay off unless you set `
|
|
53
|
+
`MCP_TRANSPORT` is needed. Mutation tools stay off unless you set `HUDU_WRITE_POLICY`.
|
|
51
54
|
|
|
52
55
|
**From npm** (recommended):
|
|
53
56
|
|
|
@@ -74,7 +77,7 @@ or in `.mcp.json` / Claude Desktop config:
|
|
|
74
77
|
`npx` fetches the package on first run and caches it; use `iit-mcp-hudu@<version>` to pin one.
|
|
75
78
|
|
|
76
79
|
**From a clone** (for development): see [Development](#development). Point your client at
|
|
77
|
-
`node /absolute/path/to/iit-mcp-hudu/dist/main.js` after `
|
|
80
|
+
`node /absolute/path/to/iit-mcp-hudu/dist/main.js` after `pnpm run build`.
|
|
78
81
|
|
|
79
82
|
## Loading credential values (read this before a live session)
|
|
80
83
|
|
|
@@ -121,7 +124,7 @@ whose envelope names a shape anomaly (`credentialShape: "anomalous"`) points bac
|
|
|
121
124
|
The server enforces the shape itself, so a bad load cannot reach the wire even if `doctor` is
|
|
122
125
|
skipped: a malformed `HUDU_API_KEY` fails boot; a malformed `X-Hudu-Api-Key` header is refused
|
|
123
126
|
before any client is built, with the typed `CREDENTIAL_MALFORMED` envelope (value-free, and
|
|
124
|
-
`retryable: false`). Neither path dials. Since node-hudu 0.
|
|
127
|
+
`retryable: false`). Neither path dials. Since node-hudu 0.14.0 the SDK enforces credential shape
|
|
125
128
|
itself too — refusing a contaminated value at client construction and attaching a value-free shape
|
|
126
129
|
diagnostic to a refused credential — and this server consumes that diagnostic directly on a 401;
|
|
127
130
|
the server's own gate stays as the boundary guard, a superset of the SDK's checks.
|
|
@@ -176,16 +179,21 @@ every tool call, with the error naming the missing header. A
|
|
|
176
179
|
credential header sent twice (which Node would otherwise join with `", "`) is treated as absent
|
|
177
180
|
for the same reason.
|
|
178
181
|
|
|
179
|
-
`
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
`HUDU_WRITE_POLICY` is the write switch. `deny` (the default) omits `hudu_write` and
|
|
183
|
+
`hudu_delete` entirely; `allow_list` permits only the canonical keys in `HUDU_WRITE_ALLOW` and
|
|
184
|
+
registers `hudu_write` only if the list holds at least one write-effect operation, and `hudu_delete`
|
|
185
|
+
only if it holds at least one destructive-effect one (per the SDK registry's effect) (a write allowed here also allows the writes it delegates to: allowing
|
|
186
|
+
`articles.patchSection` allows `articles.update`); `all` permits the SDK's whole mutation surface.
|
|
187
|
+
An empty or invalid allow-list fails startup. The SDK clients receive the same allow-list plus
|
|
188
|
+
reads needed by helper dependencies.
|
|
185
189
|
Writes default to dry-run previews; execution requires `dry_run:false`, and destructive
|
|
186
190
|
operations additionally require exact `confirm`. A confirmation string is not human consent.
|
|
187
191
|
Discovery reports deployment-policy refusals as unreachable.
|
|
188
192
|
|
|
193
|
+
`HUDU_READ_ONLY=true` is a hard override of the write policy: no `hudu_write` / `hudu_delete` tool is
|
|
194
|
+
registered and every mutation is refused, whatever `HUDU_WRITE_POLICY` and `HUDU_WRITE_ALLOW` say. Unset or
|
|
195
|
+
`false` leaves `HUDU_WRITE_POLICY` in charge (default `deny`). The effective policy is logged once at boot.
|
|
196
|
+
|
|
189
197
|
`HUDU_WRITE_POLICY` stays a **deployment-wide** decision, not a per-caller one. Per-caller
|
|
190
198
|
credentials make each caller's Hudu role an external authority too, but that authority is not ours
|
|
191
199
|
to trust and cannot be allowed to widen this server's surface: the server-side policy is the floor,
|
|
@@ -211,6 +219,19 @@ without `expand`. It is independent of `HUDU_WRITE_POLICY`: `all` does not imply
|
|
|
211
219
|
`allow` does not open writes. `hudu_search` is unaffected — it already redacts hits on those two
|
|
212
220
|
resources. Refusals are `POLICY_DENIED` and appear as unreachable rows in `hudu_list_operations`.
|
|
213
221
|
|
|
222
|
+
Password share links (node-hudu 0.16.0): `asset_passwords.listShares`, `getShare`, `createShare` and
|
|
223
|
+
`deleteShare` are credential-resource operations, so they are refused under the default
|
|
224
|
+
`HUDU_SECRET_READS=deny`; the two writes also need `HUDU_WRITE_POLICY` (or `HUDU_WRITE_ALLOW`) to permit
|
|
225
|
+
them. Under `allow`, `listShares` / `getShare` work and the share `url` (a bearer link) is masked.
|
|
226
|
+
`createShare` is never runnable here: the SDK refuses it (`POLICY_DENIED`, `reveal_required`, dry-run
|
|
227
|
+
included, no request) without its `reveal` capability, this server never grants `reveal`, and
|
|
228
|
+
`hudu_list_operations` marks the row unreachable. Link-shaped text is scrubbed from share errors.
|
|
229
|
+
|
|
230
|
+
`hudu_get_company_context` accepts `opts.skip` (`assets`, `articles`, `websites`, `asset_passwords`); pass
|
|
231
|
+
`["asset_passwords"]` when the key cannot read credentials. The skipped list comes back empty with
|
|
232
|
+
`skipped` set, and no request is made for it. `hudu_resolve_any` returns `unauthorized` (the resources the
|
|
233
|
+
credential may not read) beside the permitted hits; it errors only when every requested resource refuses.
|
|
234
|
+
|
|
214
235
|
All SDK clients keep default redaction enabled, including when `HUDU_SECRET_READS=allow`.
|
|
215
236
|
That flag permits credential-resource operations; it never enables plaintext results. The
|
|
216
237
|
SDK masks credential fields and confidential or unclassified custom fields as `[REDACTED]`.
|
|
@@ -238,17 +259,35 @@ background, so a caller's later search is body-aware without the first one await
|
|
|
238
259
|
walk (some clients enforce a 60s call timeout). Under `stdio` the one long-lived index stays warm and the
|
|
239
260
|
default is `"index"`. Pass `tier:"index"` explicitly for a complete first answer.
|
|
240
261
|
|
|
241
|
-
|
|
242
|
-
`HUDU_CACHE_PRESET` (`on` | `off`) toggles the read-through cache: the default `on` selects
|
|
243
|
-
the SDK's `recommended` per-entity TTL preset, and `off` disables it, so repeated reads
|
|
244
|
-
re-dial Hudu instead of being served from the cache.
|
|
245
|
-
`HUDU_COOLDOWN` (`on` | `off`) is the 429 cooldown gate: `off` means a 429 retries per the
|
|
246
|
-
agent profile instead of arming a local deadline whose refusals answer with `notSent: true`.
|
|
247
|
-
`HUDU_TIMEOUT_MS` (positive integer milliseconds) is the whole-call deadline, default `30000`;
|
|
248
|
-
it is always sent to the SDK explicitly, so it stays above the profile's 20 s.
|
|
262
|
+
The read-through cache (SDK `recommended` preset), the 429 cooldown gate and the 30 s whole-call deadline are fixed, not env-tunable.
|
|
249
263
|
`blockPrivateHosts` deliberately gets no override: it is the literal-IP SSRF floor for
|
|
250
264
|
caller-named origins — a security property, not a deployment preference.
|
|
251
265
|
|
|
266
|
+
## Article bodies are Markdown (node-hudu 0.14+)
|
|
267
|
+
|
|
268
|
+
`hudu_read` and `hudu_write` pass `contentFormat: 'markdown'` to the SDK, and `hudu_get_article_context`
|
|
269
|
+
asks for Markdown, so single-article bodies (`articles.get`, `articles.getContext`, `articles.resolve`,
|
|
270
|
+
`create`, `update`) are returned and accepted as Markdown. Hudu callouts (`> [!INFO]`), accordions, task
|
|
271
|
+
lists and Mermaid fences (stored as the editor-native `<pre class="language-mermaid">` form) round-trip as the dialect documents. Rows from `hudu_list` (articles),
|
|
272
|
+
`hudu_fetch_many`, `articles.list`, `articles.search` and `articles.findBySlug` still carry stored HTML:
|
|
273
|
+
those operations take no format. Rules a caller needs:
|
|
274
|
+
|
|
275
|
+
- A write's `data.content` is parsed as Markdown unless it opens with block-level HTML (`<p>`, `<div>`,
|
|
276
|
+
`<h1>`-`<h6>`, `<table>`, lists, `<pre>`, `<blockquote>`, `<hr>`, Hudu's `mce-accordion`), in which case
|
|
277
|
+
it is sent as HTML. Re-read an article through `articles.get` before editing it.
|
|
278
|
+
- A Markdown write is refused when the stored article holds something the dialect cannot express
|
|
279
|
+
(for example an inline `<svg>`); send HTML to keep it. The SDK's loss guard runs on every write.
|
|
280
|
+
- Prefer Markdown (or editor-native HTML): Hudu reshapes API-written HTML on the next UI save (h5/h6 flattened, `small`/`abbr`/`q`/`section`/`aside` unwrapped, `thead` removed, an unknown callout type becomes info).
|
|
281
|
+
- `articles.getSection` / `articles.patchSection` read or edit one section as Markdown, with dry-run.
|
|
282
|
+
- A Markdown read can be incomplete: anything the dialect cannot express (an inline `<svg>`, say) comes
|
|
283
|
+
back as plain text. `hudu_read` (`articles.get` / `getContext` / `resolve`) and
|
|
284
|
+
`hudu_get_article_context` then set `markdown_lossy: true` (with `markdown_lossy_codes`, the SDK's
|
|
285
|
+
`diffArticleRoundTrip` content-loss codes) and add one sentence to the result text; fetch the stored
|
|
286
|
+
HTML with `hudu_fetch_many` (`{items:[{resource:"articles", id}]}`) to see the full body. The read is one
|
|
287
|
+
fetch with HTML forced and converted locally (so there is no second request); a caller's own
|
|
288
|
+
`opts.format: "html"` returns the stored HTML untouched with no flag. Callouts, accordions, task
|
|
289
|
+
lists and Mermaid round-trip cleanly and are never flagged; a lossless read is unchanged.
|
|
290
|
+
|
|
252
291
|
## SDK behaviour (0.12.0)
|
|
253
292
|
|
|
254
293
|
Three SDK behaviours changed in the 0.12.0 line and shape a deployment:
|
|
@@ -284,13 +323,13 @@ Every client the server builds — the parent, the fail-closed placeholders and
|
|
|
284
323
|
(1 retry, 240/min rate limit, concurrency 3, bounded resolution scans). The rate-limit bucket
|
|
285
324
|
is per client, i.e. per scope. The preset stays a deployment-wide constant with no env
|
|
286
325
|
override; the one deadline it would otherwise govern is pinned explicitly instead
|
|
287
|
-
(
|
|
326
|
+
(a fixed 30 s, below), and the 429 now arms the cooldown gate instead of retrying
|
|
288
327
|
blindly.
|
|
289
|
-
- `cache: 'recommended'` —
|
|
328
|
+
- `cache: 'recommended'` — fixed on: the
|
|
290
329
|
per-entity read-through TTL preset (1 h structural / 5 min mutable / 1 min base). Caches are
|
|
291
330
|
per client, so per scope. A write through the write or delete client does not clear the read
|
|
292
331
|
client's cache (separate instances), so a read after a mutation is TTL-bounded stale, never
|
|
293
|
-
unbounded.
|
|
332
|
+
unbounded.
|
|
294
333
|
- `blockPrivateHosts` on the per-scope clients: the literal-IP SSRF floor for CALLER-named
|
|
295
334
|
origins, fail-closed at client construction. It complements the `HUDU_ALLOWED_BASE_HOSTS`
|
|
296
335
|
hostname matcher (which alone can express wildcard suffixes) and holds even in
|
|
@@ -299,20 +338,17 @@ Every client the server builds — the parent, the fail-closed placeholders and
|
|
|
299
338
|
env boundary and is never floor-checked, so an on-prem install whose Hudu sits on a literal
|
|
300
339
|
private IP is unaffected by the upgrade. Deliberately no env override: it is a security
|
|
301
340
|
property, not a deployment preference.
|
|
302
|
-
- `cooldown: { enabled: true }` —
|
|
341
|
+
- `cooldown: { enabled: true }` — fixed on: the 429
|
|
303
342
|
cooldown gate; a call refused locally by the gate answers with `notSent: true` in the error
|
|
304
343
|
envelope, so a model does not read a local refusal as a vendor rejection of a sent call.
|
|
305
|
-
`off` retries a 429 per the profile instead of arming a local deadline.
|
|
306
344
|
|
|
307
|
-
The whole-call deadline is
|
|
345
|
+
The whole-call deadline is a fixed `30000` ms:
|
|
308
346
|
it is always passed to the SDK explicitly, so it stays above the agent profile's 20 s
|
|
309
347
|
regardless of preset — the preset's 20 s would shorten a cold `tier:'index'` body-index walk
|
|
310
348
|
that the 0.9.2 deployment tolerated at the SDK's 30 s, and a cold-scope search must not start
|
|
311
349
|
timing out because of an upgrade.
|
|
312
350
|
|
|
313
|
-
These are deployment-wide, not per-caller
|
|
314
|
-
`HUDU_TIMEOUT_MS` are env-tunable with the defaults above, while `profile` and
|
|
315
|
-
`blockPrivateHosts` stay constants.
|
|
351
|
+
These are deployment-wide constants, not per-caller and not env-tunable.
|
|
316
352
|
|
|
317
353
|
|
|
318
354
|
## Development
|
|
@@ -320,16 +356,16 @@ These are deployment-wide, not per-caller: `HUDU_CACHE_PRESET`, `HUDU_COOLDOWN`
|
|
|
320
356
|
```
|
|
321
357
|
git clone https://github.com/msoukhomlinov/iit-mcp-hudu.git
|
|
322
358
|
cd iit-mcp-hudu
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
359
|
+
pnpm install
|
|
360
|
+
pnpm run build
|
|
361
|
+
pnpm test
|
|
362
|
+
pnpm run dev
|
|
327
363
|
```
|
|
328
364
|
|
|
329
365
|
CI (`.github/workflows/ci.yml`) runs `typecheck`, `lint` and `test` on every push and pull request.
|
|
330
366
|
|
|
331
|
-
`node-hudu` is pinned to `0.
|
|
332
|
-
review of the client authority, operation enums and redaction regressions. Docker uses `
|
|
367
|
+
`node-hudu` is pinned to `0.16.0`. The catalog hash test detects metadata drift; upgrades require
|
|
368
|
+
review of the client authority, operation enums and redaction regressions. Docker uses `pnpm install --frozen-lockfile`.
|
|
333
369
|
|
|
334
370
|
## Deployment
|
|
335
371
|
|
package/dist/auth.js
CHANGED
package/dist/base-hosts.js
CHANGED
package/dist/config.js
CHANGED
|
@@ -135,18 +135,6 @@ const schema = z
|
|
|
135
135
|
*/
|
|
136
136
|
HUDU_ALLOW_ANY_BASE_HOST: z.enum(['true', 'false']).default('false').transform((value) => value === 'true'),
|
|
137
137
|
MCP_TRANSPORT: z.enum(TRANSPORTS).default('stdio'),
|
|
138
|
-
/**
|
|
139
|
-
* Pages the body index walks per resource per build (SDK default 250 = 25,000 records).
|
|
140
|
-
*
|
|
141
|
-
* Tenant-shaped, so it is a knob rather than a constant. The walk runs articles first and assets
|
|
142
|
-
* second, and both compete for one 64 MB text budget that evicts least-recently-read text: on a
|
|
143
|
-
* tenant with tens of thousands of assets the asset text arrives last, so the ARTICLE BODIES are
|
|
144
|
-
* the first thing evicted — and article bodies are the only reason this index exists (assets
|
|
145
|
-
* carry no body, and Hudu's own ?search= already covers their titles and fields). Sizing this so
|
|
146
|
-
* the article walk completes with budget left over is what keeps searches from going body-blind.
|
|
147
|
-
* A tenant with 3,000+ articles needs about 32 pages.
|
|
148
|
-
*/
|
|
149
|
-
HUDU_SEARCH_MAX_INDEX_PAGES: z.coerce.number().int().min(1).max(1000).optional(),
|
|
150
138
|
/**
|
|
151
139
|
* The resources `hudu_search` may offer and query — the read-edge counterpart to
|
|
152
140
|
* `HUDU_WRITE_POLICY`.
|
|
@@ -156,15 +144,21 @@ const schema = z
|
|
|
156
144
|
* resources THIS credential can read so the tool never advertises a guaranteed-empty search and
|
|
157
145
|
* a model never spends a turn on a resource the key is not allowed to read. Every name is
|
|
158
146
|
* validated against the SDK's searchable set at boot, so a typo fails the container instead of a
|
|
159
|
-
* call weeks later. Applies to `hudu_search`
|
|
147
|
+
* call weeks later. Applies to `hudu_search`, `hudu_resolve_any` and the same operations through `hudu_read`; the dedicated per-resource resolve tools are unaffected.
|
|
160
148
|
*/
|
|
161
149
|
HUDU_SEARCH_RESOURCES: z.string().optional().transform(parseSearchResources),
|
|
162
150
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
151
|
+
* The single switch for mutation. `deny` (the default) registers no `hudu_write` / `hudu_delete`
|
|
152
|
+
* tool at all; `allow_list` and `all` register them and gate every call. Read effects are never
|
|
153
|
+
* touched. See {@link WRITE_POLICIES}.
|
|
165
154
|
*/
|
|
166
|
-
HUDU_READ_ONLY: z.enum(['true', 'false']).default('true').transform((value) => value === 'true'),
|
|
167
155
|
HUDU_WRITE_POLICY: z.enum(WRITE_POLICIES).default('deny'),
|
|
156
|
+
/**
|
|
157
|
+
* Hard override. `true` means no `hudu_write` / `hudu_delete` tool and no write or destructive
|
|
158
|
+
* operation, whatever `HUDU_WRITE_POLICY` / `HUDU_WRITE_ALLOW` say (see {@link applyReadOnly}).
|
|
159
|
+
* Unset or `false` leaves `HUDU_WRITE_POLICY` in charge (its default is `deny`).
|
|
160
|
+
*/
|
|
161
|
+
HUDU_READ_ONLY: z.enum(['true', 'false']).optional().transform((value) => value === 'true'),
|
|
168
162
|
/**
|
|
169
163
|
* The operation keys `HUDU_WRITE_POLICY=allow_list` permits. Read only under `allow_list`;
|
|
170
164
|
* ignored under `deny`/`all`. Exposed as the parsed array so no consumer re-splits the string.
|
|
@@ -183,33 +177,6 @@ const schema = z
|
|
|
183
177
|
HUDU_SECRET_READS: z.enum(SECRET_READ_POLICIES).default('deny'),
|
|
184
178
|
PORT: z.coerce.number().int().positive().max(65535).default(8787),
|
|
185
179
|
LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
|
|
186
|
-
/**
|
|
187
|
-
* The read-through cache preset every client this server builds gets (SDK 0.12.0).
|
|
188
|
-
*
|
|
189
|
-
* The operator-facing toggle for the read-through cache. `on` (the default) selects the
|
|
190
|
-
* SDK's `recommended` per-entity TTL preset (1 h structural / 5 min mutable / 1 min base);
|
|
191
|
-
* `off` disables the read-through cache, so a repeated read re-dials Hudu instead of
|
|
192
|
-
* being served from the cache.
|
|
193
|
-
*/
|
|
194
|
-
HUDU_CACHE_PRESET: z.enum(['on', 'off']).default('on'),
|
|
195
|
-
/**
|
|
196
|
-
* The 429 cooldown gate (SDK 0.12.0).
|
|
197
|
-
*
|
|
198
|
-
* `on` (the default) arms a local deadline after a vendor 429 whose retries are SPENT (the
|
|
199
|
-
* agent profile's 1 retry, so the first such call makes two wire attempts): calls the gate
|
|
200
|
-
* then refuses locally, and the error envelope marks them `notSent: true` so a local
|
|
201
|
-
* refusal stays distinguishable from a vendor rejection of a sent call. `off`
|
|
202
|
-
* retries a 429 per the profile's retry budget instead of arming a local deadline.
|
|
203
|
-
*/
|
|
204
|
-
HUDU_COOLDOWN: z.enum(['on', 'off']).default('on'),
|
|
205
|
-
/**
|
|
206
|
-
* The whole-call deadline in milliseconds (SDK 0.12.0). Positive integer.
|
|
207
|
-
*
|
|
208
|
-
* Defaults to 30000 and is always passed to the SDK explicitly, so it wins over the
|
|
209
|
-
* agent profile's 20 s deadline regardless of preset; the default keeps the tolerance
|
|
210
|
-
* a cold `tier:'index'` body-index walk had before the 0.12.0 adoption.
|
|
211
|
-
*/
|
|
212
|
-
HUDU_TIMEOUT_MS: z.coerce.number().int().positive().default(30000),
|
|
213
180
|
})
|
|
214
181
|
.superRefine((c, ctx) => {
|
|
215
182
|
// Pre-dial integrity check (2026-10-07 Autotask incident class): a credential whose bytes were
|
|
@@ -371,15 +338,28 @@ function checkCredentialShape(value, ctx, path) {
|
|
|
371
338
|
* @throws {ConfigError} when any variable is missing or malformed. The message lists every problem
|
|
372
339
|
* at once — a boot loop that surfaces one missing variable per restart wastes real minutes.
|
|
373
340
|
*/
|
|
341
|
+
/** Variables removed in 0.3.0. A stale one must fail boot rather than be ignored silently. */
|
|
342
|
+
const REMOVED_VARS = ['HUDU_CACHE_PRESET', 'HUDU_COOLDOWN', 'HUDU_TIMEOUT_MS', 'HUDU_SEARCH_MAX_INDEX_PAGES'];
|
|
374
343
|
export function loadConfig(env = process.env) {
|
|
344
|
+
const stale = REMOVED_VARS.filter((name) => env[name] !== undefined);
|
|
345
|
+
if (stale.length > 0) {
|
|
346
|
+
throw new ConfigError(`Invalid configuration:\n ${stale.join(', ')}: removed in 0.3.0; delete it. HUDU_WRITE_POLICY (deny | allow_list | all) governs writes, with HUDU_READ_ONLY=true as a hard override.`);
|
|
347
|
+
}
|
|
375
348
|
const parsed = schema.safeParse(env);
|
|
376
349
|
if (parsed.success)
|
|
377
350
|
return parsed.data;
|
|
378
351
|
const problems = parsed.error.issues.map((i) => ` ${i.path.join('.') || '(config)'}: ${i.message}`);
|
|
379
352
|
throw new ConfigError(`Invalid configuration:\n${problems.join('\n')}`);
|
|
380
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* The configuration the server actually runs with: `HUDU_READ_ONLY=true` forces the policy to
|
|
356
|
+
* `deny` (no write or delete tool is registered and every write is refused), whatever
|
|
357
|
+
* `HUDU_WRITE_POLICY` / `HUDU_WRITE_ALLOW` say. Otherwise the config is returned unchanged.
|
|
358
|
+
*/
|
|
359
|
+
export function applyReadOnly(config) {
|
|
360
|
+
return config.HUDU_READ_ONLY ? { ...config, HUDU_WRITE_POLICY: 'deny', HUDU_WRITE_ALLOW: [] } : config;
|
|
361
|
+
}
|
|
381
362
|
/** Thrown only by {@link loadConfig}. Never carries a credential value — only variable names. */
|
|
382
363
|
export class ConfigError extends Error {
|
|
383
364
|
name = 'ConfigError';
|
|
384
365
|
}
|
|
385
|
-
//# sourceMappingURL=config.js.map
|
package/dist/credential.js
CHANGED
package/dist/doctor.js
CHANGED
package/dist/http.js
CHANGED
package/dist/lifecycle.js
CHANGED
package/dist/logger.js
CHANGED
package/dist/main.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* Nothing else lives here. Both transports register the same tool surface from the same factory
|
|
6
6
|
* (`createMcpServerFactory`), so "which transport" is the only decision this file makes.
|
|
7
7
|
*/
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
8
9
|
import { createMcpHandler } from '@modelcontextprotocol/server';
|
|
9
10
|
import { ConfigError, loadConfig } from './config.js';
|
|
11
|
+
import { logEffectiveWritePolicy } from './policy.js';
|
|
10
12
|
import { runDoctor } from './doctor.js';
|
|
11
13
|
import { createLogger } from './logger.js';
|
|
12
14
|
import { installHttpLifecycle } from './lifecycle.js';
|
|
@@ -43,7 +45,27 @@ function runDoctorCommand() {
|
|
|
43
45
|
// the dial the report just said not to make.
|
|
44
46
|
process.exit(report.ok ? 0 : 1);
|
|
45
47
|
}
|
|
48
|
+
const USAGE = `iit-mcp-hudu: MCP server for Hudu IT documentation
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
iit-mcp-hudu serve (MCP_TRANSPORT=stdio|http, configured by environment)
|
|
52
|
+
iit-mcp-hudu doctor zero-wire credential preflight
|
|
53
|
+
iit-mcp-hudu --version print the version
|
|
54
|
+
iit-mcp-hudu --help print this help
|
|
55
|
+
|
|
56
|
+
Configuration is environment-only; see .env.example in the package.
|
|
57
|
+
`;
|
|
46
58
|
function main() {
|
|
59
|
+
// Answered before config is loaded, so they work with no credentials.
|
|
60
|
+
const flag = process.argv[2];
|
|
61
|
+
if (flag === '--version' || flag === '-v') {
|
|
62
|
+
process.stdout.write(`${createRequire(import.meta.url)('../package.json').version}\n`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (flag === '--help' || flag === '-h') {
|
|
66
|
+
process.stdout.write(USAGE);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
47
69
|
// `doctor` is matched before anything else, so it never builds a logger, a transport or a client.
|
|
48
70
|
if (process.argv[2] === 'doctor') {
|
|
49
71
|
runDoctorCommand();
|
|
@@ -64,6 +86,7 @@ function main() {
|
|
|
64
86
|
throw err;
|
|
65
87
|
}
|
|
66
88
|
const log = createLogger(config.LOG_LEVEL);
|
|
89
|
+
logEffectiveWritePolicy(config, log, process.env);
|
|
67
90
|
if (config.MCP_TRANSPORT === 'http'
|
|
68
91
|
&& config.HUDU_BASE_URL === undefined
|
|
69
92
|
&& config.HUDU_ALLOWED_BASE_HOSTS === undefined) {
|
|
@@ -89,4 +112,3 @@ function main() {
|
|
|
89
112
|
installHttpLifecycle(server, log);
|
|
90
113
|
}
|
|
91
114
|
main();
|
|
92
|
-
//# sourceMappingURL=main.js.map
|