@warlock.js/ai-tools 5.34.0 → 5.34.2
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 +16 -0
- package/README.md +1 -1
- package/cjs/index.cjs +97 -40
- package/cjs/index.cjs.map +1 -1
- package/esm/contracts/index.d.mts +2 -2
- package/esm/contracts/mcp-serve.type.d.mts +26 -1
- package/esm/contracts/mcp-serve.type.d.mts.map +1 -1
- package/esm/index.d.mts +3 -3
- package/esm/mcp/index.d.mts +1 -1
- package/esm/mcp/index.mjs.map +1 -1
- package/esm/mcp/json-schema-to-standard.d.mts +6 -3
- package/esm/mcp/json-schema-to-standard.d.mts.map +1 -1
- package/esm/mcp/json-schema-to-standard.mjs +15 -26
- package/esm/mcp/json-schema-to-standard.mjs.map +1 -1
- package/esm/mcp/serve-http.mjs +84 -15
- package/esm/mcp/serve-http.mjs.map +1 -1
- package/llms-full.txt +76 -3
- package/llms.txt +2 -1
- package/package.json +2 -2
- package/skills/connect-mcp-server/SKILL.md +1 -1
- package/skills/expose-as-mcp-server/SKILL.md +50 -2
- package/skills/index.md +1 -1
- package/skills/README.md +0 -17
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to `@warlock.js/ai-tools` are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). `@warlock.js/*` packages are released in lockstep — every package shares the same version number, so a version below may list only the changes that affected this package.
|
|
6
6
|
|
|
7
|
+
## 5.34.2 - 2026-10-19
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Qualify custom-tool guidance as a topic of @warlock.js/ai, remove the redundant loose skill catalog, and include package orientation in LLM projections.
|
|
12
|
+
|
|
13
|
+
## 5.34.1 - 2026-10-19
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- HTTP `transport.onAuthRefusal` customizes denied authentication responses for the MCP endpoint and side routes using typed safe context, 4xx/5xx statuses, allowlisted headers and JSON bodies. Host/Origin guards retain precedence, invalid or throwing hooks retain the default refusal, and handlers never run after a denial.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Tools adapted from a remote MCP server now show the model their real input schema. `jsonSchemaToStandard` implements Standard JSON Schema (`~standard.jsonSchema`), so `extractJsonSchema` and every provider adapter read the server's `inputSchema` instead of falling back to a parameterless `{ type: "object" }`, which made models guess argument names and retry. Adapters receive copies of the schema; a tool without one advertises `{ type: "object", properties: {} }`.
|
|
22
|
+
|
|
7
23
|
## 5.34.0 - 2026-10-19
|
|
8
24
|
|
|
9
25
|
### Added
|
package/README.md
CHANGED
|
@@ -162,7 +162,7 @@ if (error instanceof HttpPolicyError && error.type === "host-not-allowed") {
|
|
|
162
162
|
|
|
163
163
|
## Skills
|
|
164
164
|
|
|
165
|
-
Progressive-disclosure, per-task docs live under [`skills/`](./skills/
|
|
165
|
+
Progressive-disclosure, per-task docs live under [`skills/`](./skills/index.md):
|
|
166
166
|
|
|
167
167
|
- [`use-web-and-http-tools/`](./skills/use-web-and-http-tools/SKILL.md) — wire `web_search`, `fetch_url`,
|
|
168
168
|
`http_request`, `calculator`, and `date_time` into an agent under their guardrails.
|
package/cjs/index.cjs
CHANGED
|
@@ -587,42 +587,31 @@ function makeCompiler(schema) {
|
|
|
587
587
|
};
|
|
588
588
|
}
|
|
589
589
|
/**
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* `tool({ input })` exactly like a native seal schema.
|
|
594
|
-
*
|
|
595
|
-
* Validation behavior:
|
|
596
|
-
* - **Valid input** → `{ value }` (the input is passed through unchanged;
|
|
597
|
-
* Ajv validates, it does not transform).
|
|
598
|
-
* - **Invalid input** → `{ issues }` carrying Ajv's `instancePath` +
|
|
599
|
-
* message per failure, so `tool()` produces a `SchemaValidationError`.
|
|
600
|
-
* - **Missing `ajv` peer** → a single issue carrying the curated install
|
|
601
|
-
* string, surfaced the same way (a developer-facing message in logs).
|
|
602
|
-
* - **No / empty schema** → an accept-all passthrough (an MCP tool may
|
|
603
|
-
* advertise no `inputSchema`).
|
|
604
|
-
*
|
|
605
|
-
* @param schema - The JSON Schema (an MCP tool's `inputSchema`), or
|
|
606
|
-
* `undefined` for a no-argument tool.
|
|
607
|
-
* @returns A `StandardSchemaV1<TInput>` ready for `tool({ input })`.
|
|
608
|
-
*
|
|
609
|
-
* @example
|
|
610
|
-
* const input = jsonSchemaToStandard<{ q: string }>({
|
|
611
|
-
* type: "object",
|
|
612
|
-
* properties: { q: { type: "string" } },
|
|
613
|
-
* required: ["q"],
|
|
614
|
-
* });
|
|
590
|
+
* Standard JSON Schema converter returning a copy of the wrapped schema, so
|
|
591
|
+
* provider adapters (which may compact or rewrite it) never mutate the source.
|
|
592
|
+
* The remote server owns its schema, so every target receives it unchanged.
|
|
615
593
|
*/
|
|
594
|
+
function exposeSchema(schema) {
|
|
595
|
+
return {
|
|
596
|
+
input: () => structuredClone(schema),
|
|
597
|
+
output: () => structuredClone(schema)
|
|
598
|
+
};
|
|
599
|
+
}
|
|
616
600
|
function jsonSchemaToStandard(schema) {
|
|
617
601
|
if (!schema || Object.keys(schema).length === 0) return { "~standard": {
|
|
618
602
|
version: 1,
|
|
619
603
|
vendor: VENDOR$2,
|
|
620
|
-
validate: (value) => ({ value })
|
|
604
|
+
validate: (value) => ({ value }),
|
|
605
|
+
jsonSchema: exposeSchema({
|
|
606
|
+
type: "object",
|
|
607
|
+
properties: {}
|
|
608
|
+
})
|
|
621
609
|
} };
|
|
622
610
|
const compile = makeCompiler(schema);
|
|
623
611
|
return { "~standard": {
|
|
624
612
|
version: 1,
|
|
625
613
|
vendor: VENDOR$2,
|
|
614
|
+
jsonSchema: exposeSchema(schema),
|
|
626
615
|
async validate(value) {
|
|
627
616
|
await loadAjv();
|
|
628
617
|
if (!isAjvAvailable) return { issues: [{ message: AJV_INSTALL_INSTRUCTIONS }] };
|
|
@@ -1621,6 +1610,7 @@ function resolveSettings(transport) {
|
|
|
1621
1610
|
tokenDigests: tokens.map(digest),
|
|
1622
1611
|
authorize: auth?.authorize,
|
|
1623
1612
|
authenticate: auth?.authenticate,
|
|
1613
|
+
onAuthRefusal: transport.onAuthRefusal,
|
|
1624
1614
|
authRequired,
|
|
1625
1615
|
allowedOrigins: new Set((transport.allowedOrigins ?? []).map(normalizeOrigin)),
|
|
1626
1616
|
allowedHosts: new Set((transport.allowedHosts ?? []).map((name) => hostnameOf(name))),
|
|
@@ -1661,6 +1651,52 @@ function sendRefusal(response, status, code, message, headers = {}) {
|
|
|
1661
1651
|
}
|
|
1662
1652
|
}, headers);
|
|
1663
1653
|
}
|
|
1654
|
+
const AUTH_REFUSAL_HEADERS = /* @__PURE__ */ new Set([
|
|
1655
|
+
"www-authenticate",
|
|
1656
|
+
"cache-control",
|
|
1657
|
+
"retry-after",
|
|
1658
|
+
"content-language",
|
|
1659
|
+
"x-request-id"
|
|
1660
|
+
]);
|
|
1661
|
+
/** Snapshot plain JSON, rejecting cycles and values JSON would silently coerce/drop. */
|
|
1662
|
+
function copyRefusalBody(value, ancestors = /* @__PURE__ */ new Set()) {
|
|
1663
|
+
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
1664
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
1665
|
+
if (typeof value !== "object" || value === null || ancestors.has(value)) throw new TypeError("Invalid auth refusal body.");
|
|
1666
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1667
|
+
if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) throw new TypeError("Invalid auth refusal body.");
|
|
1668
|
+
ancestors.add(value);
|
|
1669
|
+
const result = Array.isArray(value) ? Array.from(value, (entry) => copyRefusalBody(entry, ancestors)) : Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, copyRefusalBody(entry, ancestors)]));
|
|
1670
|
+
ancestors.delete(value);
|
|
1671
|
+
return result;
|
|
1672
|
+
}
|
|
1673
|
+
/** A custom response can only end a denied exchange; it cannot authorize dispatch. */
|
|
1674
|
+
async function sendAuthRefusal(response, settings, context) {
|
|
1675
|
+
const challenge = settings.tokenDigests.length > 0 || settings.authenticate ? { "www-authenticate": "Bearer realm=\"mcp\"" } : {};
|
|
1676
|
+
if (settings.onAuthRefusal) try {
|
|
1677
|
+
const result = await settings.onAuthRefusal(Object.freeze(context));
|
|
1678
|
+
if (!isRecord(result)) throw new TypeError("Invalid auth refusal response.");
|
|
1679
|
+
const customStatus = result.status;
|
|
1680
|
+
const status = customStatus === void 0 ? 401 : customStatus;
|
|
1681
|
+
if (typeof status !== "number" || !Number.isInteger(status) || status < 400 || status > 599) throw new TypeError("Invalid auth refusal status.");
|
|
1682
|
+
const headers = { ...challenge };
|
|
1683
|
+
const customHeaders = result.headers;
|
|
1684
|
+
if (customHeaders !== void 0) {
|
|
1685
|
+
if (!isRecord(customHeaders)) throw new TypeError("Invalid auth refusal headers.");
|
|
1686
|
+
const prototype = Object.getPrototypeOf(customHeaders);
|
|
1687
|
+
if (prototype !== Object.prototype && prototype !== null) throw new TypeError("Invalid auth refusal headers.");
|
|
1688
|
+
for (const [name, value] of Object.entries(customHeaders)) {
|
|
1689
|
+
const normalized = name.toLowerCase();
|
|
1690
|
+
if (!AUTH_REFUSAL_HEADERS.has(normalized) || typeof value !== "string") throw new TypeError("Invalid auth refusal header.");
|
|
1691
|
+
(0, node_http.validateHeaderValue)(normalized, value);
|
|
1692
|
+
headers[normalized] = value;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
sendJson(response, status, copyRefusalBody(result.body), headers);
|
|
1696
|
+
return;
|
|
1697
|
+
} catch {}
|
|
1698
|
+
sendRefusal(response, 401, JSON_RPC_SERVER_ERROR, "Unauthorized.", challenge);
|
|
1699
|
+
}
|
|
1664
1700
|
/** Write an empty-bodied status (202 / 405). */
|
|
1665
1701
|
function sendEmpty(response, status, headers = {}) {
|
|
1666
1702
|
if (response.headersSent || response.destroyed) return;
|
|
@@ -1776,8 +1812,12 @@ async function handleRequest(request, response, settings, dispatch, signal, requ
|
|
|
1776
1812
|
if (request.socket.remoteAddress !== void 0) info.remoteAddress = request.socket.remoteAddress;
|
|
1777
1813
|
const authentication = await authorized(info, settings);
|
|
1778
1814
|
if (!authentication.allowed) {
|
|
1779
|
-
|
|
1780
|
-
|
|
1815
|
+
await sendAuthRefusal(response, settings, {
|
|
1816
|
+
check: authentication.check,
|
|
1817
|
+
method,
|
|
1818
|
+
path,
|
|
1819
|
+
target: routes ? "route" : "mcp"
|
|
1820
|
+
});
|
|
1781
1821
|
return;
|
|
1782
1822
|
}
|
|
1783
1823
|
if (routes) {
|
|
@@ -1925,21 +1965,38 @@ async function authorized(info, settings) {
|
|
|
1925
1965
|
const presented = digest(match?.[1]?.trim() ?? "");
|
|
1926
1966
|
let matched = false;
|
|
1927
1967
|
for (const expected of settings.tokenDigests) matched = (0, node_crypto.timingSafeEqual)(presented, expected) || matched;
|
|
1928
|
-
if (!match || !matched) return {
|
|
1968
|
+
if (!match || !matched) return {
|
|
1969
|
+
allowed: false,
|
|
1970
|
+
check: "bearer"
|
|
1971
|
+
};
|
|
1929
1972
|
}
|
|
1930
|
-
try {
|
|
1931
|
-
if (
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
allowed: true,
|
|
1936
|
-
principal
|
|
1937
|
-
};
|
|
1938
|
-
}
|
|
1939
|
-
return { allowed: true };
|
|
1973
|
+
if (settings.authorize) try {
|
|
1974
|
+
if (await settings.authorize(info) !== true) return {
|
|
1975
|
+
allowed: false,
|
|
1976
|
+
check: "authorize"
|
|
1977
|
+
};
|
|
1940
1978
|
} catch {
|
|
1941
|
-
return {
|
|
1979
|
+
return {
|
|
1980
|
+
allowed: false,
|
|
1981
|
+
check: "authorize"
|
|
1982
|
+
};
|
|
1983
|
+
}
|
|
1984
|
+
if (settings.authenticate) try {
|
|
1985
|
+
const principal = await settings.authenticate(info);
|
|
1986
|
+
return principal === void 0 ? {
|
|
1987
|
+
allowed: false,
|
|
1988
|
+
check: "authenticate"
|
|
1989
|
+
} : {
|
|
1990
|
+
allowed: true,
|
|
1991
|
+
principal
|
|
1992
|
+
};
|
|
1993
|
+
} catch {
|
|
1994
|
+
return {
|
|
1995
|
+
allowed: false,
|
|
1996
|
+
check: "authenticate"
|
|
1997
|
+
};
|
|
1942
1998
|
}
|
|
1999
|
+
return { allowed: true };
|
|
1943
2000
|
}
|
|
1944
2001
|
/**
|
|
1945
2002
|
* Stateless HTTP has no connection/session id. Repeat the caller's credential
|