@rulvar/core 1.168.0 → 1.169.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/dist/index.d.ts +30 -0
- package/dist/index.js +47 -4
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10809,6 +10809,36 @@ interface McpConfig {
|
|
|
10809
10809
|
approval?: boolean | Record<string, boolean>;
|
|
10810
10810
|
/** Host-supplied risk labels for imported tools. */
|
|
10811
10811
|
risk?: Record<string, ToolRisk>;
|
|
10812
|
+
/**
|
|
10813
|
+
* Cap on WIRE tools accepted from the tools/list sweep (RV1515),
|
|
10814
|
+
* checked after each page, PRE-filter: the sweep itself is the
|
|
10815
|
+
* resource being bounded, so allow/deny cannot admit past it. A
|
|
10816
|
+
* server that streams more refuses typed. Positive integer; absent =
|
|
10817
|
+
* unbounded (today's behavior).
|
|
10818
|
+
*/
|
|
10819
|
+
maxTools?: number;
|
|
10820
|
+
/**
|
|
10821
|
+
* Per ADMITTED tool (allow/deny filter first): the UTF-8 byte length
|
|
10822
|
+
* of the serialized inputSchema plus outputSchema when present
|
|
10823
|
+
* (RV1515). An oversized tool refuses the resolution typed, naming
|
|
10824
|
+
* the tool and its measured bytes; deny the tool or raise the cap.
|
|
10825
|
+
* Positive integer; absent = unbounded.
|
|
10826
|
+
*/
|
|
10827
|
+
maxSchemaBytes?: number;
|
|
10828
|
+
/**
|
|
10829
|
+
* Per-source latency bounds (RV1515). connectMs races the transport
|
|
10830
|
+
* handshake (on expiry the client, and for stdio its child, is
|
|
10831
|
+
* released and the refusal is typed). listMs and callMs ride the SDK
|
|
10832
|
+
* request timeout per tools/list page and per tools/call; without
|
|
10833
|
+
* them the SDK's own 60s default request timeout applies. A call
|
|
10834
|
+
* timeout surfaces as the tool's error result, never past policy.
|
|
10835
|
+
* Each a positive finite number of milliseconds.
|
|
10836
|
+
*/
|
|
10837
|
+
timeouts?: {
|
|
10838
|
+
connectMs?: number;
|
|
10839
|
+
listMs?: number;
|
|
10840
|
+
callMs?: number;
|
|
10841
|
+
};
|
|
10812
10842
|
}
|
|
10813
10843
|
/**
|
|
10814
10844
|
* The ToolSource returned by {@link mcp}: the frozen ToolSource seam
|
package/dist/index.js
CHANGED
|
@@ -3879,7 +3879,24 @@ function buildToolContext(seed) {
|
|
|
3879
3879
|
*
|
|
3880
3880
|
* Docs: https://docs.rulvar.com/guide/mcp.
|
|
3881
3881
|
*/
|
|
3882
|
+
function validateBounds(cfg) {
|
|
3883
|
+
const positiveInt = (key) => {
|
|
3884
|
+
const value = cfg[key];
|
|
3885
|
+
if (value !== void 0 && (!Number.isInteger(value) || value <= 0)) throw new ConfigError(`mcp: '${key}' must be a positive integer, got ${String(value)}`);
|
|
3886
|
+
};
|
|
3887
|
+
positiveInt("maxTools");
|
|
3888
|
+
positiveInt("maxSchemaBytes");
|
|
3889
|
+
for (const key of [
|
|
3890
|
+
"connectMs",
|
|
3891
|
+
"listMs",
|
|
3892
|
+
"callMs"
|
|
3893
|
+
]) {
|
|
3894
|
+
const value = cfg.timeouts?.[key];
|
|
3895
|
+
if (value !== void 0 && (!Number.isFinite(value) || value <= 0)) throw new ConfigError(`mcp: 'timeouts.${key}' must be a positive finite number of milliseconds, got ${String(value)}`);
|
|
3896
|
+
}
|
|
3897
|
+
}
|
|
3882
3898
|
function validateConfig(cfg) {
|
|
3899
|
+
validateBounds(cfg);
|
|
3883
3900
|
const forbid = (key) => {
|
|
3884
3901
|
if (cfg[key] !== void 0) throw new ConfigError(`mcp: '${key}' is not a config key of the '${cfg.transport}' transport (exactly the keys matching the chosen transport)`);
|
|
3885
3902
|
};
|
|
@@ -3947,7 +3964,7 @@ function mcp(cfg) {
|
|
|
3947
3964
|
name: "rulvar",
|
|
3948
3965
|
version: "1.0.0"
|
|
3949
3966
|
});
|
|
3950
|
-
|
|
3967
|
+
const attach = async () => {
|
|
3951
3968
|
if (cfg.transport === "stdio") {
|
|
3952
3969
|
const transport = new StdioClientTransport({
|
|
3953
3970
|
command: cfg.command ?? "",
|
|
@@ -3962,6 +3979,23 @@ function mcp(cfg) {
|
|
|
3962
3979
|
await cfg.server.connect(serverTransport);
|
|
3963
3980
|
await client.connect(clientTransport);
|
|
3964
3981
|
}
|
|
3982
|
+
};
|
|
3983
|
+
try {
|
|
3984
|
+
const budgetMs = cfg.timeouts?.connectMs;
|
|
3985
|
+
if (budgetMs === void 0) await attach();
|
|
3986
|
+
else {
|
|
3987
|
+
let timer;
|
|
3988
|
+
const expired = new Promise((_resolve, reject) => {
|
|
3989
|
+
timer = setTimeout(() => {
|
|
3990
|
+
reject(new ConfigError(`mcp: connect to '${sourceIdOf(cfg)}' timed out after ${budgetMs}ms`));
|
|
3991
|
+
}, budgetMs);
|
|
3992
|
+
});
|
|
3993
|
+
try {
|
|
3994
|
+
await Promise.race([attach(), expired]);
|
|
3995
|
+
} finally {
|
|
3996
|
+
clearTimeout(timer);
|
|
3997
|
+
}
|
|
3998
|
+
}
|
|
3965
3999
|
} catch (error) {
|
|
3966
4000
|
await client.close().catch(() => void 0);
|
|
3967
4001
|
throw error;
|
|
@@ -3975,13 +4009,20 @@ function mcp(cfg) {
|
|
|
3975
4009
|
const listAll = async (client) => {
|
|
3976
4010
|
const tools = [];
|
|
3977
4011
|
let cursor;
|
|
4012
|
+
const listOptions = cfg.timeouts?.listMs === void 0 ? void 0 : { timeout: cfg.timeouts.listMs };
|
|
3978
4013
|
do {
|
|
3979
|
-
const page = await client.listTools(cursor === void 0 ? {} : { cursor });
|
|
4014
|
+
const page = await client.listTools(cursor === void 0 ? {} : { cursor }, listOptions);
|
|
3980
4015
|
tools.push(...page.tools);
|
|
4016
|
+
if (cfg.maxTools !== void 0 && tools.length > cfg.maxTools) throw new ConfigError(`mcp: tools/list of '${sourceIdOf(cfg)}' returned at least ${tools.length} wire tools, over the declared maxTools ${cfg.maxTools}; raise the cap or trim the server`);
|
|
3981
4017
|
cursor = page.nextCursor;
|
|
3982
4018
|
} while (cursor !== void 0 && cursor !== "");
|
|
3983
4019
|
return tools;
|
|
3984
4020
|
};
|
|
4021
|
+
const enforceSchemaBytes = (wire) => {
|
|
4022
|
+
if (cfg.maxSchemaBytes === void 0) return;
|
|
4023
|
+
const bytes = Buffer.byteLength(JSON.stringify(wire.inputSchema) + (wire.outputSchema === void 0 ? "" : JSON.stringify(wire.outputSchema)), "utf8");
|
|
4024
|
+
if (bytes > cfg.maxSchemaBytes) throw new ConfigError(`mcp: tool '${wire.name}' declares ${bytes} bytes of schema, over the declared maxSchemaBytes ${cfg.maxSchemaBytes}; deny the tool or raise the cap`);
|
|
4025
|
+
};
|
|
3985
4026
|
const needsApprovalFor = (originalName) => {
|
|
3986
4027
|
if (cfg.approval === void 0) return false;
|
|
3987
4028
|
if (typeof cfg.approval === "boolean") return cfg.approval;
|
|
@@ -4001,7 +4042,7 @@ function mcp(cfg) {
|
|
|
4001
4042
|
const result = await client.callTool({
|
|
4002
4043
|
name: wire.name,
|
|
4003
4044
|
arguments: input ?? {}
|
|
4004
|
-
});
|
|
4045
|
+
}, void 0, cfg.timeouts?.callMs === void 0 ? void 0 : { timeout: cfg.timeouts.callMs });
|
|
4005
4046
|
if (result.isError === true) throw new Error(errorText(result));
|
|
4006
4047
|
if (result.structuredContent !== void 0) {
|
|
4007
4048
|
if (wire.outputSchema !== void 0) {
|
|
@@ -4026,7 +4067,9 @@ function mcp(cfg) {
|
|
|
4026
4067
|
const wireTools = await listAll(client);
|
|
4027
4068
|
const denySet = new Set(cfg.deny ?? []);
|
|
4028
4069
|
const allowSet = cfg.allow === void 0 ? void 0 : new Set(cfg.allow);
|
|
4029
|
-
const
|
|
4070
|
+
const admitted = wireTools.filter((wire) => !denySet.has(wire.name) && (allowSet === void 0 || allowSet.has(wire.name)));
|
|
4071
|
+
for (const wire of admitted) enforceSchemaBytes(wire);
|
|
4072
|
+
const defs = admitted.map((wire) => toDef(client, wire));
|
|
4030
4073
|
if (generation === fetchedAt) cache = defs;
|
|
4031
4074
|
return defs;
|
|
4032
4075
|
})();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.169.0",
|
|
4
4
|
"description": "Rulvar core: L0 contracts, journal kernel, ctx primitives, agent runtime, model router, tool system, dynamic orchestrator, InMemory and JSONL stores, event stream.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|