@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
142 lines (141 loc) • 5.95 kB
JavaScript
;
/**
* Outbound Delegation Header Injection
*
* Wraps globalThis.fetch to inject delegation proof headers on outbound
* HTTP requests made during delegated tool handler execution.
*
* Reads delegation context from AsyncLocalStorage (via getContext()),
* builds a signed Ed25519 JWT, and injects the canonical KYA-OS-* Layer 2
* delegation headers:
* KYA-OS-Delegation-Chain, KYA-OS-Delegation-Proof, KYA-OS-Granted-Scopes,
* and KYA-OS-Delegation-Credential when a DelegationCredential VC is present.
*
* Behavior:
* - Skips injection for internal hostnames (localhost, *.vouched.id, Fly IPs)
* - On signing failure: logs warning, continues WITHOUT delegation headers
* - Anonymous/non-delegated calls: no headers injected
*
* Related Spec: DIF MCP-I §8 — Outbound Delegation Propagation
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isInternalDelegationTarget = isInternalDelegationTarget;
exports.installDelegationInterceptor = installDelegationInterceptor;
// C4 drain: delegation-layer symbol stays on @kya-os/mcp-i-core (src/delegation/** not relocated to mcp-i-runtime - pinned by E3.5 #2904).
const mcp_i_core_1 = require("@kya-os/mcp-i-core");
// C2/C3: @kya-os/mcp-i is CJS; @kya-os/mcp is ESM-only (TS1479 blocks static import).
const request_context_js_1 = require("./request-context.js");
// Flag to avoid double-installing
const DELEGATION_INTERCEPTED_FLAG = "__kyaDelegationIntercepted";
/**
* Determine if a URL is an internal request that should not carry
* delegation headers. Matches the same guard as the compute interceptor.
*/
function isInternalDelegationTarget(urlString) {
let url;
try {
url = new URL(urlString);
}
catch {
return false;
}
const hostname = url.hostname.toLowerCase();
if (hostname === "localhost" ||
hostname === "127.0.0.1" ||
hostname === "[::1]" ||
hostname === "::1" ||
hostname === "0.0.0.0") {
return true;
}
if (hostname.endsWith(".vouched.id") ||
hostname === "vouched.id") {
return true;
}
// Internal hostnames (*.internal, bare "internal")
if (hostname.endsWith(".internal") || hostname === "internal") {
return true;
}
// AWS EC2 metadata service
if (hostname === "169.254.169.254") {
return true;
}
// Fly.io private network ranges (fdaa:*, fd10::*)
if (hostname.startsWith("fdaa:") || hostname.startsWith("fd10:")) {
return true;
}
return false;
}
/**
* Install the delegation header interceptor on globalThis.fetch.
* Returns a teardown function that restores the original fetch.
*
* Idempotent — calling multiple times is safe.
*/
function installDelegationInterceptor(logger) {
if (globalThis.fetch[DELEGATION_INTERCEPTED_FLAG]) {
logger?.debug("Delegation interceptor already installed, skipping");
return () => { };
}
const originalFetch = globalThis.fetch;
const delegationFetch = async function (input, init) {
const urlString = typeof input === "string"
? input
: input instanceof URL
? input.toString()
: input.url;
if (isInternalDelegationTarget(urlString)) {
return originalFetch(input, init);
}
const baseHeaders = input instanceof Request ? input.headers : undefined;
const headers = new Headers(init?.headers ?? baseHeaders);
// Only inject if we have a delegation context and haven't already injected
if (!headers.has("KYA-OS-Delegation-Proof")) {
const ctx = (0, request_context_js_1.getContext)();
if (ctx?.delegationRef &&
ctx.delegationPrivateKeyJwk &&
ctx.delegationAgentKid) {
try {
let targetHostname = "";
try {
targetHostname = new URL(urlString).hostname;
}
catch {
// malformed URL — leave aud empty
}
const proof = await (0, mcp_i_core_1.buildDelegationProofJWT)({
agentDid: ctx.session?.serverDid ?? "",
userDid: ctx.session?.userDid ?? "",
delegationId: ctx.delegationRef,
delegationChain: ctx.delegationChain ?? ctx.delegationRef,
scopes: ctx.delegationScopes ?? [],
privateKeyJwk: ctx.delegationPrivateKeyJwk,
kid: ctx.delegationAgentKid,
targetHostname,
});
headers.set("KYA-OS-Delegation-Chain", ctx.delegationChain ?? ctx.delegationRef);
headers.set("KYA-OS-Delegation-Proof", proof);
headers.set("KYA-OS-Granted-Scopes", (ctx.delegationScopes ?? []).join(","));
// Emit the JWS-compact DelegationCredential VC when the context carries one.
if (ctx.delegationCredential) {
headers.set("KYA-OS-Delegation-Credential", ctx.delegationCredential);
}
logger?.debug("Delegation headers injected", {
delegationRef: ctx.delegationRef,
targetHostname,
});
}
catch (error) {
logger?.warn(`Failed to build delegation proof, continuing without delegation headers: ${error.message}`);
}
}
}
return originalFetch(input, { ...init, headers });
};
delegationFetch[DELEGATION_INTERCEPTED_FLAG] = true;
globalThis.fetch = delegationFetch;
return () => {
if (globalThis.fetch === delegationFetch) {
globalThis.fetch = originalFetch;
}
};
}