iit-mcp-hudu 0.4.0 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.5.0
6
+
7
+ **Upgrading from 0.4.0:** clients now see `markdown_lossy` / `markdown_lossy_codes` on `articles.getSection`, with a section-specific note (the signal describes the whole article, so the loss may be in another section). Whole-article read text is unchanged. `hudu_list` compact rows are unchanged. node-hudu 0.17.0 -> 0.18.0, plan hash re-stamped to `9d110798...`, operation set unchanged (236).
8
+
9
+ - `articles.getSection` lifts the SDK's whole-body `content_loss` into `markdown_lossy` / `markdown_lossy_codes` like the other article reads; `content_loss` is never passed through. This supersedes the 0.4.0 note that section reads carry no signal (#42, #39).
10
+ - `COMPACT_FIELDS` is built from the SDK's `SUMMARY_FIELDS` plus explicit extras (`created_at`, `url`), with a test that derives from the SDK and a literal pin of the final lists, so SDK drift fails CI. Served rows are byte-identical (#42, #39).
11
+ - Dependency: node-hudu 0.17.0 -> 0.18.0 (exact pin). Plan hash re-stamped to `9d110798...`; operation set unchanged (236).
12
+
3
13
  ## 0.4.0
4
14
 
5
15
  **Upgrading from 0.3.0:** `hudu_list` now returns compact summary rows by default. Callers that read custom `fields`, article `content` or layout `fields` from list rows must pass `expand: true`. Everything else is additive or a fix.
package/README.md CHANGED
@@ -31,7 +31,7 @@ four old names. Rows are compact summaries by default (id, name, key scalar fiel
31
31
  25-row assets page under 20,000 characters instead of ~88,000; pass `expand: true` for the full
32
32
  records (`hudu_read` `articles.list` rows are always full). Article rows carry no body unless expanded. Paging is unchanged (`asset_layouts` still takes no `page_size`).
33
33
 
34
- These tools run on node-hudu 0.17.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.
34
+ These tools run on node-hudu 0.18.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.
35
35
 
36
36
  `hudu_fetch_many` (node-hudu 0.12.0) fetches up to 20 single records across mixed resources in
37
37
  one call: each item is exactly `{ resource, id, fields? }` — one of the 22 bare-id resources, a
@@ -281,10 +281,10 @@ those operations take no format. Rules a caller needs:
281
281
  (for example a `<video>`); send HTML to keep it. The SDK's loss guard runs on every write.
282
282
  - 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).
283
283
  - `articles.getSection` / `articles.patchSection` read or edit one section as Markdown, with dry-run.
284
- `articles.getSection` carries no `markdown_lossy` signal (the SDK drops `content_loss` inside it, and a
285
- section is not the whole body); read the whole article with `articles.get` to learn whether it is lossy.
284
+ `articles.getSection` carries the same `markdown_lossy` / `markdown_lossy_codes` signal as `articles.get`;
285
+ it describes the whole article, so the loss may be in another section.
286
286
  - A Markdown read can be incomplete: anything the dialect cannot express is dropped or reduced
287
- (`<video>` and `<script>` are dropped entirely). `hudu_read` (`articles.get` / `getContext` / `resolve`) and
287
+ (`<video>` and `<script>` are dropped entirely). `hudu_read` (`articles.get` / `getContext` / `getSection` / `resolve`) and
288
288
  `hudu_get_article_context` then set `markdown_lossy: true` (with `markdown_lossy_codes`, the SDK's
289
289
  `diffArticleRoundTrip` content-loss codes) and add one sentence to the result text; fetch the stored
290
290
  HTML with `hudu_fetch_many` (`{items:[{resource:"articles", id}]}`) to see the full body. The read is one
@@ -371,7 +371,7 @@ pnpm run dev
371
371
 
372
372
  CI (`.github/workflows/ci.yml`) runs `typecheck`, `lint` and `test` on every push and pull request.
373
373
 
374
- `node-hudu` is pinned to `0.17.0`. The catalog hash test detects metadata drift; upgrades require
374
+ `node-hudu` is pinned to `0.18.0`. The catalog hash test detects metadata drift; upgrades require
375
375
  review of the client authority, operation enums and redaction regressions. Docker uses `pnpm install --frozen-lockfile`.
376
376
 
377
377
  ## Deployment
@@ -14,6 +14,8 @@ const RECORD_PATHS = {
14
14
  // resolve answers the record itself; the `{ value }` envelope is the helper form of the same read.
15
15
  'articles.resolve': [[], ['value']],
16
16
  'articles.getContext': [['article']],
17
+ // The SDK reports getSection loss for the WHOLE body (the loss may sit in another section).
18
+ 'articles.getSection': [[]],
17
19
  };
18
20
  const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
19
21
  const take = (node, path, codes) => {
@@ -43,4 +45,4 @@ export function takeContentLoss(operation, result) {
43
45
  return { result: stripped, codes: [...codes].sort() };
44
46
  }
45
47
  /** The one short sentence appended to the result text when a read lost content. */
46
- export const lossyNote = (codes) => ` markdown_lossy: true (${codes.join(', ')}): the Markdown body is incomplete; fetch the stored HTML with hudu_fetch_many {items:[{resource:"articles", id}]} to see all of it.`;
48
+ export const lossyNote = (codes, operation) => ` markdown_lossy: true (${codes.join(', ')}): ${operation === 'articles.getSection' ? 'the Markdown read of this article is incomplete (the loss may be in another section)' : 'the Markdown body is incomplete'}; fetch the stored HTML with hudu_fetch_many {items:[{resource:"articles", id}]} to see all of it.`;
@@ -120,7 +120,7 @@ export function registerDispatchTools(server, ctx) {
120
120
  }
121
121
  const simulated = result !== null && typeof result === 'object' && result.simulated === true;
122
122
  // A Markdown article read that lost content says so; a lossless read is byte-identical to before.
123
- return ok((simulated ? `Dry run: nothing was written (${operation}).` : `${operation} completed.`) + (lossy.length ? lossyNote(lossy) : ''), {
123
+ return ok((simulated ? `Dry run: nothing was written (${operation}).` : `${operation} completed.`) + (lossy.length ? lossyNote(lossy, operation) : ''), {
124
124
  simulated,
125
125
  operation,
126
126
  result: result ?? null,
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod/v4';
2
+ import { SUMMARY_FIELDS } from 'node-hudu';
2
3
  import { getCapability } from 'node-hudu/capabilities';
3
4
  import { configError } from 'node-hudu/mcp';
4
5
  import { assertSecretReadPermitted, assertWritePermitted } from '../policy.js';
@@ -96,14 +97,28 @@ function assertNonSearchableListReadPermitted(operation, ctx) {
96
97
  assertSecretReadPermitted(operation, config, log);
97
98
  }
98
99
  /**
99
- * Compact row fields per resource: the node-hudu `*Summary` shapes plus `created_at`. The SDK exports no
100
- * runtime projection, so `test/tools/lists-compact.test.ts` only pins these lists to a literal copy: it forces a reviewer to revisit them on a node-hudu bump, it does not detect SDK drift itself. `hudu_list` projects each row onto these unless `expand: true`.
100
+ * Extras beyond the SDK's `SUMMARY_FIELDS` (the keys of the node-hudu `*Summary` shapes). Timestamps go last
101
+ * in the served row, so `updated_at` (an SDK key where present) is moved after these extras.
102
+ */
103
+ const COMPACT_EXTRAS = {
104
+ companies: ['created_at'],
105
+ articles: ['url', 'created_at'],
106
+ assets: ['created_at'],
107
+ asset_layouts: ['url', 'created_at', 'updated_at'],
108
+ };
109
+ const compactFields = (r) => {
110
+ const base = SUMMARY_FIELDS[r];
111
+ return [...base.filter((k) => k !== 'updated_at'), ...COMPACT_EXTRAS[r], ...(base.includes('updated_at') ? ['updated_at'] : [])];
112
+ };
113
+ /**
114
+ * Compact row fields per resource: `SUMMARY_FIELDS` plus `COMPACT_EXTRAS`. `test/tools/lists-compact.test.ts`
115
+ * derives them from the SDK and also pins the final lists literally. `hudu_list` projects each row onto these unless `expand: true`.
101
116
  */
102
117
  export const COMPACT_FIELDS = {
103
- companies: ['id', 'name', 'nickname', 'slug', 'website', 'phone_number', 'city', 'state', 'id_number', 'archived', 'url', 'created_at', 'updated_at'],
104
- articles: ['id', 'name', 'slug', 'company_id', 'folder_id', 'draft', 'enable_sharing', 'url', 'created_at', 'updated_at'],
105
- assets: ['id', 'name', 'company_id', 'company_name', 'asset_layout_id', 'primary_serial', 'asset_type', 'archived', 'url', 'created_at', 'updated_at'],
106
- asset_layouts: ['id', 'name', 'slug', 'active', 'icon', 'color', 'url', 'created_at', 'updated_at'],
118
+ companies: compactFields('companies'),
119
+ articles: compactFields('articles'),
120
+ assets: compactFields('assets'),
121
+ asset_layouts: compactFields('asset_layouts'),
107
122
  };
108
123
  /** Project rows onto the compact fields; a field the row lacks is omitted, not nulled. */
109
124
  export function compactRows(resource, rows) {
package/dist/tools.js CHANGED
@@ -30,4 +30,4 @@ export { MAX_SCOPED_CLIENTS, ScopedClientCache, createMcpServerFactory } from '.
30
30
  * it against the installed `CATALOG_PLAN_HASH`, so the upgrade fails CI instead of quietly shipping
31
31
  * a tool surface that no longer describes the SDK behind it.
32
32
  */
33
- export const BUILT_AGAINST_PLAN_HASH = '1ad5e23b9c140e8f9cf60ac710dd508fc1109e86b9a0620b2d0925f16b11d7b8';
33
+ export const BUILT_AGAINST_PLAN_HASH = '9d11079847a9e2a8d73f4a075a4004b49f59624be5ffc8253636fc41cf677702';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iit-mcp-hudu",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "MCP server for Hudu IT documentation, built on the node-hudu SDK",
5
5
  "license": "MIT",
6
6
  "author": "Max Soukhomlinov",
@@ -43,7 +43,7 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@modelcontextprotocol/server": "^2.3.1",
46
- "node-hudu": "0.17.0",
46
+ "node-hudu": "0.18.0",
47
47
  "zod": "^4.6.7"
48
48
  },
49
49
  "devDependencies": {