UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

120 lines (119 loc) 5.15 kB
"use strict"; /** * Outbound Identity Bridge (mcp-i → @kya-os/compute interceptor) * * Bridges this package's AsyncLocalStorage request context into the compute * outbound-identity interceptor so the Layer-2 delegation headers and the * `KYA-OS-Session-Id` anchor are emitted on the compute (OpenClaw sidecar) * path — resolving the long-standing `interceptor.ts:183-188` TODO. * * Why a global rendezvous (not a direct import): neither package depends on * the other (compute must not import the SDK; the SDK must not pull in the * Fly.io sidecar). When the compute interceptor installs it publishes a * registrar on `Symbol.for("kya-os.outbound-identity.registry")`; this module * reads that registrar and registers providers backed by `getContext()`. When * compute is absent (pure-Node / Cloudflare deployments) the registrar is * missing and this is a clean no-op — those paths inject delegation headers via * their own fetch wrap (`outbound-delegation.ts`). * * Authority model follows ADR-001: the DelegationCredential VC is * authoritative; `-Chain` / `-Granted-Scopes` are advisory hints. * * Related Spec: DIF MCP-I §8 — Outbound Delegation Propagation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ensureOutboundIdentityBridge = ensureOutboundIdentityBridge; exports.__resetOutboundIdentityBridgeForTests = __resetOutboundIdentityBridgeForTests; const mcp_i_core_1 = require("@kya-os/mcp-i-core"); const request_context_1 = require("./request-context"); /** * Must match `OUTBOUND_IDENTITY_REGISTRY_KEY` in * `@kya-os/compute`'s `outbound-identity/interceptor.ts`. `Symbol.for` resolves * the same registry symbol in both packages within one process. */ const OUTBOUND_IDENTITY_REGISTRY_KEY = Symbol.for("kya-os.outbound-identity.registry"); // Idempotency guard — the registered providers are closures over getContext(), // so a single registration covers all subsequent requests in this process. let bridged = false; function readRegistry() { // REASON: globalThis has no index signature for our Symbol key; one localized // cast reads the typed cross-package slot compute published at install. return globalThis[OUTBOUND_IDENTITY_REGISTRY_KEY]; } /** * Build a compute-compatible delegation provider from the request context. * Returns undefined per-call when the active context carries no delegation (or * lacks the signing material), so anonymous / non-delegated calls inject no * delegation headers. */ function delegationProvider() { const ctx = (0, request_context_1.getContext)(); if (!ctx?.delegationRef || !ctx.delegationPrivateKeyJwk || !ctx.delegationAgentKid) { return undefined; } const delegationChain = ctx.delegationChain ?? ctx.delegationRef; const delegationScopes = ctx.delegationScopes ?? []; const agentDid = ctx.session?.serverDid ?? ""; const userDid = ctx.session?.userDid ?? ""; // Surface the calling client's DID (xmcp-i#413) so the compute interceptor // can attach it to KTA attestations. Spread conditionally below so the field // is absent (not `undefined`) when the session carries no client identity. const clientDid = ctx.session?.clientDid; const privateKeyJwk = ctx.delegationPrivateKeyJwk; const kid = ctx.delegationAgentKid; const delegationRef = ctx.delegationRef; return { delegationRef, delegationChain, delegationScopes, agentDid, userDid, ...(clientDid ? { clientDid } : {}), kid, delegationCredential: ctx.delegationCredential, buildProof: (targetHostname) => (0, mcp_i_core_1.buildDelegationProofJWT)({ agentDid, userDid, delegationId: delegationRef, delegationChain, scopes: delegationScopes, privateKeyJwk, kid, targetHostname, }), }; } /** Resolve the stable MCP session id from the active request context. */ function sessionIdProvider() { return (0, request_context_1.getContext)()?.session?.sessionId; } /** * Register the AsyncLocalStorage-backed delegation / session providers with the * compute outbound-identity interceptor, if it is installed in this process. * * Idempotent and best-effort: safe to call from multiple init sites. Returns * true when the registrar was found and providers were wired, false otherwise. */ function ensureOutboundIdentityBridge(logger) { if (bridged) return true; const registry = readRegistry(); if (!registry?.setDelegationProvider) { logger?.debug("Outbound identity bridge: compute interceptor not present — skipping"); return false; } registry.setDelegationProvider(delegationProvider); registry.setSessionIdProvider?.(sessionIdProvider); bridged = true; logger?.debug("Outbound identity bridge: delegation + session providers registered"); return true; } /** * Test-only reset of the idempotency guard. Not part of the public runtime API. * @internal */ function __resetOutboundIdentityBridgeForTests() { bridged = false; }