@rulvar/core 1.169.0 → 1.170.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 +22 -0
- package/dist/index.js +28 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10839,6 +10839,28 @@ interface McpConfig {
|
|
|
10839
10839
|
listMs?: number;
|
|
10840
10840
|
callMs?: number;
|
|
10841
10841
|
};
|
|
10842
|
+
/**
|
|
10843
|
+
* streamable-http only (RV1516): headers injected into EVERY wire
|
|
10844
|
+
* request through a wrapped fetch. The hook form is awaited before
|
|
10845
|
+
* each send, so it IS the refresh point: rotate a token in the hook
|
|
10846
|
+
* and the next request carries it, with no reconnect and no
|
|
10847
|
+
* library-invented 401 retry (transport failures surface exactly as
|
|
10848
|
+
* before; the engine's RetryPolicy owns retries).
|
|
10849
|
+
*/
|
|
10850
|
+
http?: {
|
|
10851
|
+
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
|
|
10852
|
+
};
|
|
10853
|
+
/**
|
|
10854
|
+
* What a listChanged notification means for THIS source (RV1516).
|
|
10855
|
+
* 'rekey' is the documented default: the session cache invalidates
|
|
10856
|
+
* and subsequently spawned agents import the changed list under a new
|
|
10857
|
+
* toolsetHash. 'refuse' fails closed instead: the notification
|
|
10858
|
+
* poisons the source, every later tools() call refuses typed, and
|
|
10859
|
+
* only close() (a deliberate host reset) clears it. In-flight spawn
|
|
10860
|
+
* snapshots are untouched either way. Composes with the toolset
|
|
10861
|
+
* attestation: refuse at the source vs refuse at the spawn.
|
|
10862
|
+
*/
|
|
10863
|
+
drift?: "rekey" | "refuse";
|
|
10842
10864
|
}
|
|
10843
10865
|
/**
|
|
10844
10866
|
* The ToolSource returned by {@link mcp}: the frozen ToolSource seam
|
package/dist/index.js
CHANGED
|
@@ -3894,6 +3894,9 @@ function validateBounds(cfg) {
|
|
|
3894
3894
|
const value = cfg.timeouts?.[key];
|
|
3895
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
3896
|
}
|
|
3897
|
+
if (cfg.drift !== void 0 && cfg.drift !== "rekey" && cfg.drift !== "refuse") throw new ConfigError(`mcp: 'drift' must be 'rekey' or 'refuse', got '${String(cfg.drift)}'`);
|
|
3898
|
+
const headers = cfg.http?.headers;
|
|
3899
|
+
if (headers !== void 0 && typeof headers !== "function" && typeof headers !== "object") throw new ConfigError("mcp: 'http.headers' must be a record of header values or a (possibly async) function returning one");
|
|
3897
3900
|
}
|
|
3898
3901
|
function validateConfig(cfg) {
|
|
3899
3902
|
validateBounds(cfg);
|
|
@@ -3905,6 +3908,7 @@ function validateConfig(cfg) {
|
|
|
3905
3908
|
if (cfg.command === void 0) throw new ConfigError("mcp: the stdio transport requires 'command'");
|
|
3906
3909
|
forbid("url");
|
|
3907
3910
|
forbid("server");
|
|
3911
|
+
forbid("http");
|
|
3908
3912
|
return;
|
|
3909
3913
|
case "streamable-http":
|
|
3910
3914
|
if (cfg.url === void 0) throw new ConfigError("mcp: the streamable-http transport requires 'url'");
|
|
@@ -3917,6 +3921,7 @@ function validateConfig(cfg) {
|
|
|
3917
3921
|
forbid("command");
|
|
3918
3922
|
forbid("args");
|
|
3919
3923
|
forbid("url");
|
|
3924
|
+
forbid("http");
|
|
3920
3925
|
return;
|
|
3921
3926
|
default: throw new ConfigError(`mcp: unknown transport '${String(cfg.transport)}'`);
|
|
3922
3927
|
}
|
|
@@ -3937,6 +3942,23 @@ function mapContent(result) {
|
|
|
3937
3942
|
text: block.text ?? ""
|
|
3938
3943
|
} : block);
|
|
3939
3944
|
}
|
|
3945
|
+
/**
|
|
3946
|
+
* Wraps fetch so EVERY wire request of the streamable-http transport
|
|
3947
|
+
* consults the declared headers before send (RV1516): the hook form is
|
|
3948
|
+
* the per-request refresh point for rotating tokens, so no reconnect
|
|
3949
|
+
* and no library-invented 401 retry exists or is needed.
|
|
3950
|
+
*/
|
|
3951
|
+
function perRequestHeaders(headersOption) {
|
|
3952
|
+
return async (url, init) => {
|
|
3953
|
+
const extra = typeof headersOption === "function" ? await headersOption() : headersOption;
|
|
3954
|
+
const headers = new Headers(init?.headers);
|
|
3955
|
+
for (const [name, value] of Object.entries(extra ?? {})) headers.set(name, value);
|
|
3956
|
+
return fetch(url, {
|
|
3957
|
+
...init,
|
|
3958
|
+
headers
|
|
3959
|
+
});
|
|
3960
|
+
};
|
|
3961
|
+
}
|
|
3940
3962
|
function errorText(result) {
|
|
3941
3963
|
const text = (result.content ?? []).filter((block) => block.type === "text").map((block) => block.text ?? "").join("");
|
|
3942
3964
|
return text === "" ? "MCP tool reported an error" : text;
|
|
@@ -3959,6 +3981,7 @@ function mcp(cfg) {
|
|
|
3959
3981
|
let cache;
|
|
3960
3982
|
let generation = 0;
|
|
3961
3983
|
let inFlight;
|
|
3984
|
+
let poisoned = false;
|
|
3962
3985
|
const connect = async () => {
|
|
3963
3986
|
const client = new Client({
|
|
3964
3987
|
name: "rulvar",
|
|
@@ -3972,7 +3995,8 @@ function mcp(cfg) {
|
|
|
3972
3995
|
});
|
|
3973
3996
|
await client.connect(transport);
|
|
3974
3997
|
} else if (cfg.transport === "streamable-http") {
|
|
3975
|
-
const
|
|
3998
|
+
const declaredHeaders = cfg.http?.headers;
|
|
3999
|
+
const transport = new StreamableHTTPClientTransport(new URL(cfg.url ?? ""), declaredHeaders === void 0 ? void 0 : { fetch: perRequestHeaders(declaredHeaders) });
|
|
3976
4000
|
await client.connect(transport);
|
|
3977
4001
|
} else {
|
|
3978
4002
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
@@ -4003,6 +4027,7 @@ function mcp(cfg) {
|
|
|
4003
4027
|
client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
4004
4028
|
generation += 1;
|
|
4005
4029
|
cache = void 0;
|
|
4030
|
+
if (cfg.drift === "refuse") poisoned = true;
|
|
4006
4031
|
});
|
|
4007
4032
|
return client;
|
|
4008
4033
|
};
|
|
@@ -4058,6 +4083,7 @@ function mcp(cfg) {
|
|
|
4058
4083
|
return {
|
|
4059
4084
|
id: sourceIdOf(cfg),
|
|
4060
4085
|
tools: async () => {
|
|
4086
|
+
if (poisoned) throw new ConfigError(`mcp: the tool list of '${sourceIdOf(cfg)}' changed after import (listChanged) and drift policy 'refuse' holds the source closed; close() and re-create the source (and re-record any toolset attestation) to import the changed list deliberately`);
|
|
4061
4087
|
if (cache !== void 0) return cache;
|
|
4062
4088
|
if (inFlight !== void 0) return inFlight;
|
|
4063
4089
|
const fetch = (async () => {
|
|
@@ -4084,6 +4110,7 @@ function mcp(cfg) {
|
|
|
4084
4110
|
const pending = clientPromise;
|
|
4085
4111
|
clientPromise = void 0;
|
|
4086
4112
|
cache = void 0;
|
|
4113
|
+
poisoned = false;
|
|
4087
4114
|
if (pending === void 0) return;
|
|
4088
4115
|
let client;
|
|
4089
4116
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rulvar/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.170.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",
|