open-managed-agents 0.1.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/LICENSE +93 -0
- package/README.md +248 -0
- package/bin/oma.mjs +499 -0
- package/dist/scripts/alpha-openai-compatible-fixture.mjs +134 -0
- package/dist/scripts/alpha-smoke-models.mjs +92 -0
- package/dist/scripts/alpha-smoke.mjs +581 -0
- package/dist/scripts/oma-doctor.js +230 -0
- package/dist/scripts/oma-models.js +299 -0
- package/dist/scripts/oma-workspaces.js +133 -0
- package/dist/src/control-plane/admin/auth.js +43 -0
- package/dist/src/control-plane/admin/routes.js +76 -0
- package/dist/src/control-plane/admin/service.js +102 -0
- package/dist/src/control-plane/admission.js +90 -0
- package/dist/src/control-plane/agents/routes.js +63 -0
- package/dist/src/control-plane/agents/service.js +687 -0
- package/dist/src/control-plane/agents/store.js +391 -0
- package/dist/src/control-plane/agents/types.js +1 -0
- package/dist/src/control-plane/api-constants.js +3 -0
- package/dist/src/control-plane/app.js +907 -0
- package/dist/src/control-plane/console/auth.js +68 -0
- package/dist/src/control-plane/console/static.js +112 -0
- package/dist/src/control-plane/deployment-runtime-config.js +328 -0
- package/dist/src/control-plane/deployment-runtime-event-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-runtime-turn-guard.js +14 -0
- package/dist/src/control-plane/deployment-session-coordinator.js +25 -0
- package/dist/src/control-plane/deployment-session-output-coordinator.js +27 -0
- package/dist/src/control-plane/deployment-storage.js +354 -0
- package/dist/src/control-plane/egress/image.js +48 -0
- package/dist/src/control-plane/egress/policy.js +571 -0
- package/dist/src/control-plane/egress/presets.js +77 -0
- package/dist/src/control-plane/egress/proxy.js +72 -0
- package/dist/src/control-plane/egress/ssrf.js +106 -0
- package/dist/src/control-plane/egress/utils/debug.js +14 -0
- package/dist/src/control-plane/egress/vendor/http-proxy.js +335 -0
- package/dist/src/control-plane/egress/vendor/mitm-ca.js +226 -0
- package/dist/src/control-plane/egress/vendor/mitm-leaf.js +124 -0
- package/dist/src/control-plane/egress/vendor/parent-proxy.js +438 -0
- package/dist/src/control-plane/egress/vendor/request-filter.js +105 -0
- package/dist/src/control-plane/egress/vendor/sandbox-config.js +1 -0
- package/dist/src/control-plane/egress/vendor/tls-terminate-proxy.js +269 -0
- package/dist/src/control-plane/environments/routes.js +54 -0
- package/dist/src/control-plane/environments/service.js +112 -0
- package/dist/src/control-plane/environments/store.js +132 -0
- package/dist/src/control-plane/environments/types.js +1 -0
- package/dist/src/control-plane/errors.js +81 -0
- package/dist/src/control-plane/events/broadcaster.js +217 -0
- package/dist/src/control-plane/events/constants.js +2 -0
- package/dist/src/control-plane/events/persist.js +39 -0
- package/dist/src/control-plane/events/request.js +190 -0
- package/dist/src/control-plane/events/routes.js +159 -0
- package/dist/src/control-plane/events/runtime-helpers.js +134 -0
- package/dist/src/control-plane/events/service.js +1985 -0
- package/dist/src/control-plane/events/session-guards.js +29 -0
- package/dist/src/control-plane/events/sse.js +6 -0
- package/dist/src/control-plane/events/store.js +772 -0
- package/dist/src/control-plane/events/tool-persistence.js +102 -0
- package/dist/src/control-plane/events/types.js +21 -0
- package/dist/src/control-plane/files/routes.js +121 -0
- package/dist/src/control-plane/files/service.js +65 -0
- package/dist/src/control-plane/files/store-common.js +87 -0
- package/dist/src/control-plane/files/store-local.js +488 -0
- package/dist/src/control-plane/files/store-memory.js +270 -0
- package/dist/src/control-plane/files/store.js +2 -0
- package/dist/src/control-plane/files/types.js +7 -0
- package/dist/src/control-plane/http.js +25 -0
- package/dist/src/control-plane/ids.js +43 -0
- package/dist/src/control-plane/logging.js +263 -0
- package/dist/src/control-plane/models/auth-storage-backend.js +238 -0
- package/dist/src/control-plane/models/catalog.js +78 -0
- package/dist/src/control-plane/models/config-security.js +376 -0
- package/dist/src/control-plane/models/deployment-config.js +93 -0
- package/dist/src/control-plane/models/routes.js +32 -0
- package/dist/src/control-plane/models/service.js +138 -0
- package/dist/src/control-plane/observability/instruments.js +65 -0
- package/dist/src/control-plane/observability/metrics.js +125 -0
- package/dist/src/control-plane/observability/routes.js +95 -0
- package/dist/src/control-plane/openapi/document.js +282 -0
- package/dist/src/control-plane/openapi/routes.js +78 -0
- package/dist/src/control-plane/request-idempotency.js +85 -0
- package/dist/src/control-plane/secrets/envelope.js +132 -0
- package/dist/src/control-plane/secrets/master-key.js +44 -0
- package/dist/src/control-plane/secrets/routes.js +20 -0
- package/dist/src/control-plane/secrets/service.js +71 -0
- package/dist/src/control-plane/secrets/store.js +212 -0
- package/dist/src/control-plane/secrets/types.js +3 -0
- package/dist/src/control-plane/sessions/pi/custom-tools.js +146 -0
- package/dist/src/control-plane/sessions/pi/mcp/bridge.js +527 -0
- package/dist/src/control-plane/sessions/pi/mcp/client.js +157 -0
- package/dist/src/control-plane/sessions/pi/mcp/credential.js +28 -0
- package/dist/src/control-plane/sessions/pi/mcp/fetch.js +69 -0
- package/dist/src/control-plane/sessions/pi/mcp/probe.js +202 -0
- package/dist/src/control-plane/sessions/pi/mcp/runtime.js +13 -0
- package/dist/src/control-plane/sessions/pi/runner.js +1104 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-glob.js +388 -0
- package/dist/src/control-plane/sessions/pi/sandbox/cma-grep.js +124 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker-egress.js +347 -0
- package/dist/src/control-plane/sessions/pi/sandbox/docker.js +1589 -0
- package/dist/src/control-plane/sessions/pi/sandbox/glob.js +48 -0
- package/dist/src/control-plane/sessions/pi/sandbox/image.js +7 -0
- package/dist/src/control-plane/sessions/pi/sandbox/microsandbox.js +1677 -0
- package/dist/src/control-plane/sessions/pi/sandbox/provider.js +561 -0
- package/dist/src/control-plane/sessions/pi/sandbox/selection.js +168 -0
- package/dist/src/control-plane/sessions/pi/span-normalizer.js +82 -0
- package/dist/src/control-plane/sessions/pi/tool-permissions.js +392 -0
- package/dist/src/control-plane/sessions/pi/translator.js +100 -0
- package/dist/src/control-plane/sessions/request.js +164 -0
- package/dist/src/control-plane/sessions/resources.js +63 -0
- package/dist/src/control-plane/sessions/routes.js +88 -0
- package/dist/src/control-plane/sessions/serialize.js +24 -0
- package/dist/src/control-plane/sessions/service.js +733 -0
- package/dist/src/control-plane/sessions/store.js +538 -0
- package/dist/src/control-plane/sessions/types.js +1 -0
- package/dist/src/control-plane/skills/archive.js +169 -0
- package/dist/src/control-plane/skills/routes.js +41 -0
- package/dist/src/control-plane/skills/service.js +29 -0
- package/dist/src/control-plane/skills/store.js +233 -0
- package/dist/src/control-plane/skills/types.js +5 -0
- package/dist/src/control-plane/sqlite-transaction.js +15 -0
- package/dist/src/control-plane/vaults/mcp-oauth-validate.js +165 -0
- package/dist/src/control-plane/vaults/oauth-refresh-ticker.js +43 -0
- package/dist/src/control-plane/vaults/oauth-refresh.js +448 -0
- package/dist/src/control-plane/vaults/routes.js +66 -0
- package/dist/src/control-plane/vaults/service.js +553 -0
- package/dist/src/control-plane/vaults/store.js +691 -0
- package/dist/src/control-plane/vaults/types.js +1 -0
- package/dist/src/control-plane/wake-loop.js +65 -0
- package/dist/src/control-plane/wiring.js +92 -0
- package/dist/src/control-plane/workspace.js +4 -0
- package/dist/src/control-plane/workspaces/store.js +158 -0
- package/dist/src/egress-proxy-main.js +103 -0
- package/dist/src/main.js +124 -0
- package/dist/src/types/agents.js +1 -0
- package/dist/src/types/common.js +1 -0
- package/dist/src/types/environments.js +1 -0
- package/dist/src/types/events.js +121 -0
- package/dist/src/types/files.js +1 -0
- package/dist/src/types/json.js +19 -0
- package/dist/src/types/sessions.js +1 -0
- package/dist/ui/managed-agents-console/README.md +52 -0
- package/dist/ui/managed-agents-console/docs/agents.md +23 -0
- package/dist/ui/managed-agents-console/docs/console.md +23 -0
- package/dist/ui/managed-agents-console/docs/dreams.md +5 -0
- package/dist/ui/managed-agents-console/docs/environments.md +27 -0
- package/dist/ui/managed-agents-console/docs/events.md +19 -0
- package/dist/ui/managed-agents-console/docs/files.md +17 -0
- package/dist/ui/managed-agents-console/docs/github.md +11 -0
- package/dist/ui/managed-agents-console/docs/integrations.md +19 -0
- package/dist/ui/managed-agents-console/docs/memory.md +11 -0
- package/dist/ui/managed-agents-console/docs/migration.md +17 -0
- package/dist/ui/managed-agents-console/docs/multiagent.md +11 -0
- package/dist/ui/managed-agents-console/docs/outcomes.md +11 -0
- package/dist/ui/managed-agents-console/docs/overview.md +41 -0
- package/dist/ui/managed-agents-console/docs/permissions.md +24 -0
- package/dist/ui/managed-agents-console/docs/quickstart.md +51 -0
- package/dist/ui/managed-agents-console/docs/reference.md +27 -0
- package/dist/ui/managed-agents-console/docs/sandbox-reference.md +19 -0
- package/dist/ui/managed-agents-console/docs/sandbox-security.md +15 -0
- package/dist/ui/managed-agents-console/docs/scheduled-deployments.md +5 -0
- package/dist/ui/managed-agents-console/docs/self-hosted-sandboxes.md +15 -0
- package/dist/ui/managed-agents-console/docs/session-operations.md +21 -0
- package/dist/ui/managed-agents-console/docs/sessions.md +21 -0
- package/dist/ui/managed-agents-console/docs/skills.md +15 -0
- package/dist/ui/managed-agents-console/docs/tools.md +23 -0
- package/dist/ui/managed-agents-console/docs/vaults.md +19 -0
- package/dist/ui/managed-agents-console/docs/webhooks.md +11 -0
- package/dist/ui/managed-agents-console/index.html +47 -0
- package/dist/ui/managed-agents-console/serve.mjs +116 -0
- package/dist/ui/managed-agents-console/src/agents-files.jsx +273 -0
- package/dist/ui/managed-agents-console/src/api.js +1043 -0
- package/dist/ui/managed-agents-console/src/app.jsx +633 -0
- package/dist/ui/managed-agents-console/src/auth.jsx +215 -0
- package/dist/ui/managed-agents-console/src/console.css +733 -0
- package/dist/ui/managed-agents-console/src/data.js +188 -0
- package/dist/ui/managed-agents-console/src/detail.jsx +719 -0
- package/dist/ui/managed-agents-console/src/docs.jsx +200 -0
- package/dist/ui/managed-agents-console/src/environments.jsx +365 -0
- package/dist/ui/managed-agents-console/src/forms.jsx +494 -0
- package/dist/ui/managed-agents-console/src/icons.jsx +48 -0
- package/dist/ui/managed-agents-console/src/skills.jsx +21 -0
- package/dist/ui/managed-agents-console/src/sse.js +185 -0
- package/dist/ui/managed-agents-console/src/states.jsx +90 -0
- package/dist/ui/managed-agents-console/src/tweaks-panel.jsx +541 -0
- package/dist/ui/managed-agents-console/src/ui.jsx +190 -0
- package/dist/ui/managed-agents-console/src/vaults-data.js +89 -0
- package/dist/ui/managed-agents-console/src/vaults.jsx +171 -0
- package/dist/ui/managed-agents-console/vendor/babel.min.js +4 -0
- package/dist/ui/managed-agents-console/vendor/react-dom.production.min.js +267 -0
- package/dist/ui/managed-agents-console/vendor/react.production.min.js +31 -0
- package/dist/ui/openapi-docs/VENDOR.md +27 -0
- package/dist/ui/openapi-docs/index.html +16 -0
- package/dist/ui/openapi-docs/swagger-initializer.js +14 -0
- package/dist/ui/openapi-docs/vendor/LICENSE +202 -0
- package/dist/ui/openapi-docs/vendor/NOTICE +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-bundle.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui-standalone-preset.js +2 -0
- package/dist/ui/openapi-docs/vendor/swagger-ui.css +3 -0
- package/package.json +53 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
const authOperation = new AsyncLocalStorage();
|
|
3
|
+
export async function runMcpAuthOperation(fn) {
|
|
4
|
+
return authOperation.run({}, fn);
|
|
5
|
+
}
|
|
6
|
+
export function recordRejectedMcpAuthorization(snapshot) {
|
|
7
|
+
const context = authOperation.getStore();
|
|
8
|
+
if (context !== undefined)
|
|
9
|
+
context.rejectedAuthorization = snapshot;
|
|
10
|
+
}
|
|
11
|
+
const MCP_AUTH_SNAPSHOT = Symbol("oma.mcpAuthSnapshot");
|
|
12
|
+
export function attachMcpAuthSnapshot(error) {
|
|
13
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
14
|
+
const snapshot = authOperation.getStore()?.rejectedAuthorization;
|
|
15
|
+
if (snapshot !== undefined) {
|
|
16
|
+
Object.defineProperty(normalized, MCP_AUTH_SNAPSHOT, {
|
|
17
|
+
value: snapshot,
|
|
18
|
+
enumerable: false,
|
|
19
|
+
configurable: true,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
export function getMcpAuthSnapshot(error) {
|
|
25
|
+
if (!(error instanceof Error))
|
|
26
|
+
return undefined;
|
|
27
|
+
return error[MCP_AUTH_SNAPSHOT];
|
|
28
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// SSRF-guarded fetch for control-plane MCP dials (plan 0122 §4.3).
|
|
2
|
+
//
|
|
3
|
+
// Agent configs carry attacker-influenceable URLs and the control plane dials
|
|
4
|
+
// them; without a guard, `http://169.254.169.254/` or the admin API would be
|
|
5
|
+
// reachable. The transport takes a custom fetch, so we hand it one whose
|
|
6
|
+
// dialer resolves through the egress pinned lookup: every resolution is
|
|
7
|
+
// checked against the blocked ranges, and the socket connects to exactly the
|
|
8
|
+
// vetted address (Node's `lookup` seam — no re-resolution between check and
|
|
9
|
+
// connect, so no TOCTOU). Probe-verified (2026-07-06): undici re-consults the
|
|
10
|
+
// lookup per new connection, so a DNS rebind to a private range is re-checked
|
|
11
|
+
// and rejected, and `redirect: "error"` refuses the classic 30x-to-internal
|
|
12
|
+
// bypass.
|
|
13
|
+
import { isIP } from "node:net";
|
|
14
|
+
import { Agent, fetch as undiciFetch } from "undici";
|
|
15
|
+
import { createPinnedLookup, isBlockedAddress, } from "../../../egress/ssrf.js";
|
|
16
|
+
export function createGuardedMcpFetch(opts = {}) {
|
|
17
|
+
const dispatcher = new Agent({
|
|
18
|
+
connect: { lookup: ipv4FirstLookup(createPinnedLookup(opts)) },
|
|
19
|
+
});
|
|
20
|
+
return async (url, init) => {
|
|
21
|
+
// Node skips the lookup seam entirely for IP-literal hostnames, so a
|
|
22
|
+
// `http://127.0.0.1/…` target would bypass a lookup-only guard. Check
|
|
23
|
+
// literals explicitly, same as egress/proxy.ts does before dialing.
|
|
24
|
+
assertLiteralHostAllowed(url, opts);
|
|
25
|
+
return undiciFetch(url, {
|
|
26
|
+
...init,
|
|
27
|
+
// After the init spread: callers (including the MCP SDK) can never
|
|
28
|
+
// relax the dialer or re-enable redirects.
|
|
29
|
+
dispatcher,
|
|
30
|
+
redirect: "error",
|
|
31
|
+
});
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reorder vetted addresses IPv4-first. Live smoke 48: dual-stack MCP hosts
|
|
36
|
+
* (e.g. DeepWiki on AWS) resolve v6-first, and on networks without working
|
|
37
|
+
* v6 egress each new connection burned ~15s of v6 SYN timeouts before
|
|
38
|
+
* falling back — racing the MCP handshake into its request timeout. Every
|
|
39
|
+
* address here has already passed the blocked-range check; only the dial
|
|
40
|
+
* order changes.
|
|
41
|
+
*/
|
|
42
|
+
function ipv4FirstLookup(inner) {
|
|
43
|
+
return ((hostname, options, callback) => {
|
|
44
|
+
inner(hostname, options, (err, address, family) => {
|
|
45
|
+
if (err || !Array.isArray(address)) {
|
|
46
|
+
callback(err, address, family);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const sorted = [
|
|
50
|
+
...address.filter((entry) => entry.family === 4),
|
|
51
|
+
...address.filter((entry) => entry.family !== 4),
|
|
52
|
+
];
|
|
53
|
+
callback(null, sorted);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
function assertLiteralHostAllowed(url, opts) {
|
|
58
|
+
const parsed = typeof url === "string" ? new URL(url) : url;
|
|
59
|
+
// URL.hostname wraps IPv6 literals in brackets; strip for isIP.
|
|
60
|
+
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
61
|
+
const family = isIP(host);
|
|
62
|
+
if (family === 0)
|
|
63
|
+
return; // not a literal — the pinned lookup handles it
|
|
64
|
+
if (opts.allowAddress?.(host, family))
|
|
65
|
+
return;
|
|
66
|
+
if (!isBlockedAddress(host, family))
|
|
67
|
+
return;
|
|
68
|
+
throw Object.assign(new Error(`egress denied: ${host} is in a blocked (private/loopback/reserved) range`), { code: "EGRESS_SSRF_BLOCKED" });
|
|
69
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { knownSecretRepresentations, scrubKnownSecrets, } from "../../../logging.js";
|
|
3
|
+
const PROBE_REQUEST_ID = 1;
|
|
4
|
+
export async function probeMcpInitialize(url, authorization, fetch, opts) {
|
|
5
|
+
const controller = new AbortController();
|
|
6
|
+
const timeout = setTimeout(() => controller.abort(), opts.timeoutMs);
|
|
7
|
+
try {
|
|
8
|
+
const headers = new Headers({
|
|
9
|
+
accept: "application/json, text/event-stream",
|
|
10
|
+
"content-type": "application/json",
|
|
11
|
+
});
|
|
12
|
+
if (authorization !== undefined)
|
|
13
|
+
headers.set("authorization", authorization);
|
|
14
|
+
let response;
|
|
15
|
+
try {
|
|
16
|
+
response = await fetch(url, {
|
|
17
|
+
method: "POST",
|
|
18
|
+
headers,
|
|
19
|
+
body: JSON.stringify({
|
|
20
|
+
jsonrpc: "2.0",
|
|
21
|
+
id: PROBE_REQUEST_ID,
|
|
22
|
+
method: "initialize",
|
|
23
|
+
params: {
|
|
24
|
+
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
25
|
+
capabilities: {},
|
|
26
|
+
clientInfo: { name: "open-managed-agents", version: "0" },
|
|
27
|
+
},
|
|
28
|
+
}),
|
|
29
|
+
signal: controller.signal,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { reached: false };
|
|
34
|
+
}
|
|
35
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
36
|
+
let captured;
|
|
37
|
+
try {
|
|
38
|
+
captured = contentType.toLowerCase().includes("text/event-stream")
|
|
39
|
+
? await readSseInitialize(response, opts.capBytes)
|
|
40
|
+
: await readCappedBody(response, opts.capBytes);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
captured = { body: "", truncated: true };
|
|
44
|
+
}
|
|
45
|
+
const knownSecrets = opts.knownSecrets ?? [];
|
|
46
|
+
const scrubbedBody = scrubKnownSecrets(captured.body, knownSecrets);
|
|
47
|
+
return {
|
|
48
|
+
reached: true,
|
|
49
|
+
statusCode: response.status,
|
|
50
|
+
contentType: scrubKnownSecrets(contentType, knownSecrets),
|
|
51
|
+
body: captured.truncated
|
|
52
|
+
? scrubTruncatedSecretSuffix(scrubbedBody, knownSecrets)
|
|
53
|
+
: scrubbedBody,
|
|
54
|
+
bodyTruncated: captured.truncated,
|
|
55
|
+
initializeSucceeded: response.ok && isInitializeResult(captured.initializeBody ?? captured.body),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
clearTimeout(timeout);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function readCappedBody(response, capBytes) {
|
|
63
|
+
if (response.body === null)
|
|
64
|
+
return { body: "", truncated: false };
|
|
65
|
+
const reader = response.body.getReader();
|
|
66
|
+
const chunks = [];
|
|
67
|
+
let total = 0;
|
|
68
|
+
let truncated = false;
|
|
69
|
+
try {
|
|
70
|
+
while (total < capBytes) {
|
|
71
|
+
const { value, done } = await reader.read();
|
|
72
|
+
if (done)
|
|
73
|
+
break;
|
|
74
|
+
if (value === undefined)
|
|
75
|
+
continue;
|
|
76
|
+
const remaining = capBytes - total;
|
|
77
|
+
if (value.byteLength > remaining) {
|
|
78
|
+
chunks.push(value.subarray(0, remaining));
|
|
79
|
+
total += remaining;
|
|
80
|
+
truncated = true;
|
|
81
|
+
await reader.cancel();
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
chunks.push(value);
|
|
85
|
+
total += value.byteLength;
|
|
86
|
+
if (total === capBytes) {
|
|
87
|
+
const contentLengthHeader = response.headers.get("content-length");
|
|
88
|
+
const contentLength = Number(contentLengthHeader);
|
|
89
|
+
truncated =
|
|
90
|
+
contentLengthHeader === null ||
|
|
91
|
+
!Number.isFinite(contentLength) ||
|
|
92
|
+
contentLength > capBytes;
|
|
93
|
+
await reader.cancel();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
await reader.cancel().catch(() => undefined);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
reader.releaseLock();
|
|
104
|
+
}
|
|
105
|
+
return { body: decodeChunks(chunks, total), truncated };
|
|
106
|
+
}
|
|
107
|
+
async function readSseInitialize(response, capBytes) {
|
|
108
|
+
if (response.body === null)
|
|
109
|
+
return { body: "", truncated: false };
|
|
110
|
+
const reader = response.body.getReader();
|
|
111
|
+
const decoder = new TextDecoder();
|
|
112
|
+
let pending = "";
|
|
113
|
+
let bytesRead = 0;
|
|
114
|
+
let captured = "";
|
|
115
|
+
try {
|
|
116
|
+
while (bytesRead < capBytes) {
|
|
117
|
+
const { value, done } = await reader.read();
|
|
118
|
+
if (done)
|
|
119
|
+
break;
|
|
120
|
+
if (value === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
const remaining = capBytes - bytesRead;
|
|
123
|
+
const part = value.byteLength > remaining ? value.subarray(0, remaining) : value;
|
|
124
|
+
bytesRead += part.byteLength;
|
|
125
|
+
pending += decoder.decode(part, { stream: true });
|
|
126
|
+
const frames = pending.split(/\r?\n\r?\n/);
|
|
127
|
+
pending = frames.pop() ?? "";
|
|
128
|
+
for (const frame of frames) {
|
|
129
|
+
const data = frame
|
|
130
|
+
.split(/\r?\n/)
|
|
131
|
+
.filter((line) => line.startsWith("data:"))
|
|
132
|
+
.map((line) => line.slice(5).replace(/^ /, ""))
|
|
133
|
+
.join("\n");
|
|
134
|
+
if (data.length === 0)
|
|
135
|
+
continue;
|
|
136
|
+
captured = data;
|
|
137
|
+
if (isResponseForProbe(data)) {
|
|
138
|
+
await reader.cancel();
|
|
139
|
+
return { body: data, truncated: false, initializeBody: data };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (value.byteLength > remaining || bytesRead === capBytes) {
|
|
143
|
+
await reader.cancel();
|
|
144
|
+
return { body: captured || pending, truncated: true };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { body: captured || pending + decoder.decode(), truncated: false };
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
await reader.cancel().catch(() => undefined);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
reader.releaseLock();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function isResponseForProbe(text) {
|
|
158
|
+
const value = parseObject(text);
|
|
159
|
+
return value?.jsonrpc === "2.0" && value.id === PROBE_REQUEST_ID;
|
|
160
|
+
}
|
|
161
|
+
function isInitializeResult(text) {
|
|
162
|
+
const value = parseObject(text);
|
|
163
|
+
return (value?.jsonrpc === "2.0" &&
|
|
164
|
+
value.id === PROBE_REQUEST_ID &&
|
|
165
|
+
typeof value.result === "object" &&
|
|
166
|
+
value.result !== null &&
|
|
167
|
+
!Array.isArray(value.result));
|
|
168
|
+
}
|
|
169
|
+
function parseObject(text) {
|
|
170
|
+
try {
|
|
171
|
+
const value = JSON.parse(text);
|
|
172
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
173
|
+
? value
|
|
174
|
+
: undefined;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function decodeChunks(chunks, total) {
|
|
181
|
+
const bytes = new Uint8Array(total);
|
|
182
|
+
let offset = 0;
|
|
183
|
+
for (const chunk of chunks) {
|
|
184
|
+
bytes.set(chunk, offset);
|
|
185
|
+
offset += chunk.byteLength;
|
|
186
|
+
}
|
|
187
|
+
return new TextDecoder().decode(bytes);
|
|
188
|
+
}
|
|
189
|
+
function scrubTruncatedSecretSuffix(body, knownSecrets) {
|
|
190
|
+
let matchLength = 0;
|
|
191
|
+
for (const secret of knownSecrets.flatMap(knownSecretRepresentations)) {
|
|
192
|
+
for (let length = Math.min(secret.length, body.length); length > matchLength; length -= 1) {
|
|
193
|
+
if (body.endsWith(secret.slice(0, length))) {
|
|
194
|
+
matchLength = length;
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return matchLength === 0
|
|
200
|
+
? body
|
|
201
|
+
: `${body.slice(0, -matchLength)}[redacted]`;
|
|
202
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RefreshCoordinator } from "../../../vaults/oauth-refresh.js";
|
|
2
|
+
import { createGuardedMcpFetch } from "./fetch.js";
|
|
3
|
+
/** Default production composition: one guarded fetch for MCP and OAuth refresh. */
|
|
4
|
+
export function createDefaultMcpRuntime(store, fetch = createGuardedMcpFetch(), opts = {}) {
|
|
5
|
+
return {
|
|
6
|
+
fetch,
|
|
7
|
+
refreshCoordinator: new RefreshCoordinator({
|
|
8
|
+
store,
|
|
9
|
+
fetch,
|
|
10
|
+
...(opts.onScheduled === undefined ? {} : { onScheduled: opts.onScheduled }),
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
}
|