UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

142 lines (141 loc) 5.95 kB
"use strict"; /** * 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; } }; }