iit-mcp-hudu 0.8.0 → 0.9.1
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 +40 -0
- package/dist/tools/search-prose.js +1 -1
- package/dist/tools/search.js +34 -4
- package/dist/tools/typesense-probe.js +1 -0
- package/dist/tools.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.1
|
|
4
|
+
|
|
5
|
+
**Upgrading from 0.9.0:** no configuration change. This release sharpens the argument prose a calling
|
|
6
|
+
model reads without asking for help, and refuses arguments `hudu_search` does not implement.
|
|
7
|
+
|
|
8
|
+
- Change: `hudu_search`'s `company_id` description now states the filter's real semantics — a
|
|
9
|
+
**numeric** id only (a company name cannot be filtered: the Typesense tier sends the number into
|
|
10
|
+
`filter_by` and the vendor path compares the number), and omission means all companies **plus
|
|
11
|
+
global articles**, which is the wide answer rather than a narrow one. `resources` now says what the
|
|
12
|
+
list means ("only these resource types") and that fewer collections is less work. Funded by trimming
|
|
13
|
+
redundant prose in `mode` / `updated_since` / `min_score` / `query`, so the served `tools/list`
|
|
14
|
+
surface is **byte-identical in size** to 0.9.0; the frozen hashes were re-recorded and say why.
|
|
15
|
+
- Fix: the loose `http` argument object accepted and **silently ignored** any argument the tool does
|
|
16
|
+
not implement. Measured on 0.9.0: `asset_layout_id` (advertised by `mode:"resources"` as an assets
|
|
17
|
+
filter) returned unfiltered results with no signal — a dropped filter answers a different question
|
|
18
|
+
than the one asked. Unknown fields are now refused with a named error, and the looseness is kept
|
|
19
|
+
only for the policy-owned `tier` / `refresh`, where `meta.policyOverride` already discloses the
|
|
20
|
+
override. `stdio` stays strict.
|
|
21
|
+
- Documentation: the Typesense tier's 8-finding adversarial audit is closed. Seven were fixed in
|
|
22
|
+
`node-hudu` 0.22.0 (pre-scoring cap in Typesense's ranking order, non-numeric ids miscounted as a
|
|
23
|
+
page cap, a 401 from one collection masking a healthy sibling, the probe reading only `articles`,
|
|
24
|
+
the `hydrated` count's meaning, `verifyHits:false` disclosing nothing, and per-hit provenance now
|
|
25
|
+
carried by the required `source` field); Q8 was clean. Q7 (silent partial LRU body eviction under
|
|
26
|
+
`maxIndexTextBytes`) remains an unverified live-environment question and is not claimed as fixed.
|
|
27
|
+
|
|
28
|
+
## 0.9.0
|
|
29
|
+
|
|
30
|
+
**Upgrading from 0.8.0:** no configuration change. The pinned `node-hudu` moves from 0.21.0 to 0.22.0,
|
|
31
|
+
which closes four MAJOR and three MINOR defects in the Typesense body tier found by an adversarial
|
|
32
|
+
audit. Nothing changes for a deployment that leaves the tier unset.
|
|
33
|
+
|
|
34
|
+
- Add: `probeBodySearch` results now carry the additive per-collection `results[]` field
|
|
35
|
+
(`node-hudu` 0.22.0). The probe report maps it when present, so a caller can see WHICH collection
|
|
36
|
+
answered or failed instead of only the aggregate `reachable`.
|
|
37
|
+
- Change: the pinned SDK's tier is honest about its bounds. Articles are hydrated in full through a
|
|
38
|
+
bounded pool of reads (so title matches ranked 11+ reach the local scorer), `meta.typesense` splits
|
|
39
|
+
`capped` / `unreadable` / `unscored`, the circuit breaker is per collection, a 401 from one
|
|
40
|
+
collection is no longer reported as a bad key when a sibling answered, and `verifyHits:false`
|
|
41
|
+
discloses that the tier contributed no hit. See `node-hudu` 0.22.0 release notes.
|
|
42
|
+
|
|
3
43
|
## 0.8.0
|
|
4
44
|
|
|
5
45
|
**Upgrading from 0.7.0:** no configuration change for any existing deployment. Six new optional
|
|
@@ -384,7 +384,7 @@ function serverPolicyTierSentence(ctx) {
|
|
|
384
384
|
/** The `query` field description, adjusted so it does not promise a body search it cannot run. */
|
|
385
385
|
export function searchQueryDescription(ctx) {
|
|
386
386
|
const { bodySearchable, toolDefaultTier } = ctx;
|
|
387
|
-
const base = 'REQUIRED for mode="search". Partial words fine; article BODY is searched. Multi-word
|
|
387
|
+
const base = 'REQUIRED for mode="search". Partial words fine; article BODY is searched. Multi-word: topic="query" (mode="help").';
|
|
388
388
|
if (!bodySearchable) {
|
|
389
389
|
// Fail loudly: a missed anchor would advertise a body search this deployment cannot run.
|
|
390
390
|
if (!base.includes('article BODY is searched.'))
|
package/dist/tools/search.js
CHANGED
|
@@ -244,10 +244,12 @@ export function registerSearchTool(server, ctx) {
|
|
|
244
244
|
// instead of being rejected before the policy path can answer. Under `stdio` every field is
|
|
245
245
|
// served, so the object stays STRICT: an unknown argument is refused, never silently ignored.
|
|
246
246
|
const inputFields = {
|
|
247
|
+
// The tool title already names the three modes; "help documents fields" is redundant there
|
|
248
|
+
// and costs 23 B of the measured surface. The help mode's own output documents fields.
|
|
247
249
|
mode: z
|
|
248
250
|
.enum(['search', 'help', 'resources'])
|
|
249
251
|
.default('search')
|
|
250
|
-
.describe('search|help|resources
|
|
252
|
+
.describe('search|help|resources'),
|
|
251
253
|
query: z
|
|
252
254
|
.string()
|
|
253
255
|
.min(1)
|
|
@@ -265,12 +267,21 @@ export function registerSearchTool(server, ctx) {
|
|
|
265
267
|
.describe('help only; "all" adds syntax'),
|
|
266
268
|
limit: z.number().int().min(1).max(25).optional().describe('1-25, default 8; larger is CONFIG_ERROR'),
|
|
267
269
|
snippetChars: z.number().int().min(0).max(400).optional().describe('0 = no snippet.'),
|
|
268
|
-
|
|
269
|
-
|
|
270
|
+
// A name cannot be filtered: the tier sends company_id into Typesense's filter_by and the
|
|
271
|
+
// vendor path compares the number. Say so where the model reads it, and say what omission
|
|
272
|
+
// means, because "no company" is the wide answer, not a narrow one. Funded by trimming
|
|
273
|
+
// min_score/query prose so the served surface stays byte-identical (see the size gate).
|
|
274
|
+
company_id: z
|
|
275
|
+
.number()
|
|
276
|
+
.int()
|
|
277
|
+
.positive()
|
|
278
|
+
.optional()
|
|
279
|
+
.describe('Numeric company id; omit = all companies + global articles.'),
|
|
280
|
+
updated_since: z.string().optional().describe('ISO ts; newer only'),
|
|
270
281
|
min_score: z
|
|
271
282
|
.number()
|
|
272
283
|
.optional()
|
|
273
|
-
.describe('0-100;
|
|
284
|
+
.describe('0-100; floor may empty hits; meta.scanned/meta.returned; company_id/updated_since; uncounted.'),
|
|
274
285
|
exact_only: z.boolean().optional().describe('No fuzzy match.'),
|
|
275
286
|
// Under `http` neither field is served: the tier decides whether a call may wait for a
|
|
276
287
|
// corpus-wide walk, and the calling model can observe neither the index's warmth nor the
|
|
@@ -321,6 +332,25 @@ export function registerSearchTool(server, ctx) {
|
|
|
321
332
|
}
|
|
322
333
|
// A field the mode cannot honour is a CONFIG_ERROR naming the fields it does honour.
|
|
323
334
|
const bag = args;
|
|
335
|
+
// An argument this tool does not implement is REFUSED, never silently ignored. Under `http`
|
|
336
|
+
// the input object is loose ONLY so the policy-owned `tier`/`refresh` reach the handler and
|
|
337
|
+
// are reported as overridden (`meta.policyOverride`); every OTHER unknown key is a caller bug,
|
|
338
|
+
// and dropping it would answer a different question than the one asked. Measured on 0.9.0:
|
|
339
|
+
// `asset_layout_id` (advertised by `mode:"resources"` as an assets filter, but not implemented
|
|
340
|
+
// here) was accepted and ignored, returning unfiltered results with no signal. The invoke path
|
|
341
|
+
// already validates its bag against the SDK's inputSchema, so this aligns the two paths.
|
|
342
|
+
const knownFields = new Set([
|
|
343
|
+
...Object.keys(inputFields),
|
|
344
|
+
...(searchTierIsPolicy ? ['tier', 'refresh'] : []),
|
|
345
|
+
]);
|
|
346
|
+
const unknownFields = Object.keys(bag).filter((k) => bag[k] !== undefined && !knownFields.has(k));
|
|
347
|
+
if (unknownFields.length) {
|
|
348
|
+
return errorContent(configError(
|
|
349
|
+
// Kept under the 200-char model-facing error cap (`MAX_ERROR_MESSAGE_CHARS`): a longer
|
|
350
|
+
// message is truncated mid-sentence, which is worse than a shorter one that survives
|
|
351
|
+
// intact. The essential disclosure comes first, the pointer last.
|
|
352
|
+
`hudu_search: unknown argument(s) ${unknownFields.join(', ')} — a dropped filter answers a different question, so unknown fields are never silently ignored. Call mode:"help" for fields.`));
|
|
353
|
+
}
|
|
324
354
|
const provided = Object.keys(bag).filter((k) => k !== 'mode' && bag[k] !== undefined);
|
|
325
355
|
const unhonoured = provided.filter((k) => spec.rejects.includes(k));
|
|
326
356
|
if (unhonoured.length) {
|
|
@@ -51,6 +51,7 @@ export async function probeTypesense(hudu, config) {
|
|
|
51
51
|
reachable: result.reachable === true,
|
|
52
52
|
collections: Array.isArray(result.collections) && result.collections.length > 0 ? result.collections : collections,
|
|
53
53
|
probeMs: typeof result.probeMs === 'number' ? result.probeMs : Date.now() - started,
|
|
54
|
+
...(Array.isArray(result.results) && result.results.length > 0 ? { results: result.results } : {}),
|
|
54
55
|
...(result.error ? { error: { code: result.error.code, message: result.error.message } } : {}),
|
|
55
56
|
};
|
|
56
57
|
}
|
package/dist/tools.js
CHANGED
|
@@ -32,4 +32,4 @@ export { MAX_SCOPED_CLIENTS, ScopedClientCache, createMcpServerFactory } from '.
|
|
|
32
32
|
* it against the installed `CATALOG_PLAN_HASH`, so the upgrade fails CI instead of quietly shipping
|
|
33
33
|
* a tool surface that no longer describes the SDK behind it.
|
|
34
34
|
*/
|
|
35
|
-
export const BUILT_AGAINST_PLAN_HASH = '
|
|
35
|
+
export const BUILT_AGAINST_PLAN_HASH = 'b46a003d8bef3f781f1bd1e21dc27271a1ad469eaa891560c767ad5b274dfaa1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iit-mcp-hudu",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
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.
|
|
46
|
+
"node-hudu": "0.22.0",
|
|
47
47
|
"zod": "^4.6.7"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|