UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

513 lines (512 loc) 22.5 kB
"use strict"; /** * XMCP-I Runtime - Identity-Aware MCP Runtime * * Composes upstream XMCP core with identity plugin layer * according to runtime support specification and requirements. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RUNTIME_ERRORS = exports.RuntimeFactory = exports.MCPIRuntime = void 0; exports.createMCPIRuntime = createMCPIRuntime; const identity_1 = require("./identity"); const session_1 = require("./session"); const proof_1 = require("./proof"); const mcp_i_runtime_1 = require("@kya-os/mcp-i-runtime"); const well_known_1 = require("./well-known"); const debug_1 = require("./debug"); const demo_1 = require("./demo"); const delegation_verifier_1 = require("./delegation-verifier"); const auth_handshake_1 = require("./auth-handshake"); const resume_token_store_1 = require("./resume-token-store"); const request_context_1 = require("./request-context"); const outbound_identity_bridge_1 = require("./outbound-identity-bridge"); const tool_protection_1 = require("./tool-protection"); const tool_protection_registry_1 = require("./tool-protection-registry"); // 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"); /** * XMCP-I Runtime class */ /** * @deprecated Use MCPINodeRuntimeWrapper instead. * This class is maintained for backward compatibility only. */ class MCPIRuntime { identityManager; sessionManager; wellKnownManager; debugManager; demoManager; delegationVerifier; // NEW - Phase 1 resumeTokenStore; // NEW - Phase 1 toolProtectionResolver; // NEW - Phase 1.5 config; cachedIdentity; constructor(config = {}) { this.config = config; // Initialize identity manager this.identityManager = new identity_1.IdentityManager({ environment: config.identity?.environment || "development", ...config.identity, }); // Initialize session manager this.sessionManager = new session_1.SessionManager(config.session); // Initialize resume token store (NEW - Phase 1). // Durable stores (e.g. Cloudflare KV) are selected from config so a token // minted at consent time survives cold starts / restarts / multi-instance // — otherwise the user is re-prompted. Memory is the EXPLICIT single-process // dev default. Mirrors the delegation-verifier factory pattern. const resumeTtl = config.delegation?.authorization?.resumeTokenTtl || 600_000; const resumeStoreConfig = config.delegation?.authorization?.resumeTokenStore; this.resumeTokenStore = resumeStoreConfig ? (0, resume_token_store_1.createResumeTokenStore)({ ttlMs: resumeTtl, ...resumeStoreConfig }) : new auth_handshake_1.MemoryResumeTokenStore(resumeTtl); // Initialize delegation verifier (NEW - Phase 1) if (config.delegation?.enabled && config.delegation?.verifier) { this.delegationVerifier = (0, delegation_verifier_1.createDelegationVerifier)(config.delegation.verifier); } // NOTE: Tool protection resolver is created in initialize() // after identity is loaded, because AgentShield API needs the agent DID } /** * Initialize the runtime (async setup) */ async initialize() { // Perform runtime environment checks this.checkRuntimeEnvironment(); // Ensure identity is loaded this.cachedIdentity = await this.identityManager.ensureIdentity(); // Set server DID in session manager (for session context) this.sessionManager.setServerDid(this.cachedIdentity.did); // Create tool protection resolver NOW that we have the agent DID (NEW - Phase 1.5) if (this.config.delegation?.enabled) { this.toolProtectionResolver = (0, tool_protection_1.createToolProtectionResolver)({ inline: this.config.delegation.toolProtections, localFile: this.config.delegation.toolProtectionsFile, agentShield: this.config.delegation.agentShield ? { apiUrl: this.config.delegation.agentShield.apiUrl, agentDid: this.cachedIdentity.did, // ← FIX: Use actual DID! apiKey: this.config.delegation.agentShield.apiKey, } : undefined, debug: this.config.identity?.environment === 'development', }); } // Initialize well-known manager if configured if (this.config.wellKnown) { this.wellKnownManager = new well_known_1.WellKnownManager(this.cachedIdentity, this.config.wellKnown); } // Initialize debug manager in development if (this.config.identity?.environment === "development") { this.debugManager = (0, debug_1.createDebugEndpoint)(this.cachedIdentity, this.config.identity.environment); } // Initialize demo manager this.demoManager = (0, demo_1.createDemoManager)(this.cachedIdentity, { identityBadge: this.config.demo?.identityBadge || false, environment: this.config.identity?.environment || "development", }); // Bridge AsyncLocalStorage delegation/session context into the compute // outbound-identity interceptor when it is preloaded in this process. // Best-effort + idempotent — a no-op when compute is not present. (0, outbound_identity_bridge_1.ensureOutboundIdentityBridge)(); console.error(`✅ XMCP-I Runtime initialized`); console.error(` DID: ${this.cachedIdentity.did}`); console.error(` Key ID: ${this.cachedIdentity.kid}`); // Show verify link in development (default true) const showVerifyLink = this.config.runtime?.showVerifyLink !== false; demo_1.DemoConsole.printVerifyLink(showVerifyLink, this.config.identity?.environment || "development"); // Show demo warning if any demo features are enabled if (this.demoManager?.isIdentityBadgeEnabled()) { demo_1.DemoConsole.printDemoWarning(); } // Resolve tool protection configuration (NEW - Phase 1.5) if (this.toolProtectionResolver) { const toolProtections = await this.toolProtectionResolver.resolve(); // Populate global registry so compiled code can access it tool_protection_registry_1.toolProtectionRegistry.registerAll(toolProtections); tool_protection_registry_1.toolProtectionRegistry.setDelegationVerifier(this.delegationVerifier); tool_protection_registry_1.toolProtectionRegistry.setDebug(this.config.identity?.environment === 'development'); // Build auth config for the registry if (this.delegationVerifier) { const authConfig = { delegationVerifier: this.delegationVerifier, resumeTokenStore: this.resumeTokenStore, kta: this.config.delegation?.authorization?.kta, bouncer: { authorizationUrl: this.config.delegation?.authorization?.authorizationUrl || "https://agentshield.example.com/consent", resumeTokenTtl: this.config.delegation?.authorization?.resumeTokenTtl, minReputationScore: this.config.delegation?.authorization?.minReputationScore, requireAuthForUnknown: this.config.delegation?.authorization?.requireAuthForUnknown, }, debug: this.config.identity?.environment === "development", }; tool_protection_registry_1.toolProtectionRegistry.setAuthConfig(authConfig); } if (this.config.identity?.environment === 'development') { const protectedTools = tool_protection_registry_1.toolProtectionRegistry.getProtectedTools(); console.error('✅ Tool protection configuration loaded'); if (protectedTools.length > 0) { console.error(` Protected tools: ${protectedTools.join(', ')}`); } } } } /** * Validate handshake and create session */ async validateHandshake(request) { const result = await this.sessionManager.validateHandshake(request); if (!result.success) { console.error(`Handshake validation failed: ${result.error?.message}`); if (result.error?.remediation) { console.error(`Remediation: ${result.error.remediation}`); } return null; } return result.session; } /** * Process tool call with identity-aware proof generation * * NEW (Phase 1): Includes delegation verification interceptor */ async processToolCall(request, session, toolHandler, options = {}) { if (!this.cachedIdentity) { throw new Error("Runtime not initialized - call initialize() first"); } // PHASE 1 INTERCEPTOR: Check delegation BEFORE executing tool if (this.config.delegation?.enabled && options.requiresDelegation) { if (!this.delegationVerifier) { throw new Error("Delegation verifier not configured"); } if (!options.agentDid) { throw new Error("agentDid required when requiresDelegation=true"); } const requiredScopes = options.requiredScopes || []; // Build auth handshake config const authConfig = { delegationVerifier: this.delegationVerifier, resumeTokenStore: this.resumeTokenStore, kta: this.config.delegation.authorization?.kta, bouncer: { authorizationUrl: this.config.delegation.authorization?.authorizationUrl || "https://agentshield.example.com/consent", resumeTokenTtl: this.config.delegation.authorization?.resumeTokenTtl, minReputationScore: this.config.delegation.authorization?.minReputationScore, requireAuthForUnknown: this.config.delegation.authorization?.requireAuthForUnknown, }, debug: this.config.identity?.environment === "development", }; // Verify delegation or return authorization hints const verifyResult = await (0, auth_handshake_1.verifyOrHints)(options.agentDid, requiredScopes, authConfig); // If not authorized, return needs_authorization error if (!verifyResult.authorized) { if (!verifyResult.authError) { throw new Error('Authorization failed but no authError was provided'); } return verifyResult.authError; } // If authorized, capture delegation metadata for outbound header propagation if (verifyResult.delegation) { options.delegationRef = verifyResult.delegation.id; options.delegationChain = (0, mcp_i_core_1.buildChainString)(verifyResult.delegation); options.delegationScopes = options.requiredScopes ?? []; const identity = await this.identityManager.ensureIdentity(); (0, request_context_1.setDelegationContextFromIdentity)({ delegationId: options.delegationRef, delegationChain: options.delegationChain, delegationScopes: options.delegationScopes, // JWS-compact DelegationCredential VC from the verified result — // emitted on KYA-OS-Delegation-Credential when the verifier // returned a presentable credential. delegationCredential: verifyResult.credential?.credential_jwt, identity, }); } } const auditInvocation = this.config.auditTrail ? await (0, mcp_i_runtime_1.beginRuntimeToolAudit)(this.config.auditTrail, { toolName: request.method, args: request.params, session: { ...session, id: session.sessionId, }, }) : undefined; // Capture before async boundary — TS narrowing doesn't survive across async callbacks const cachedIdentity = this.cachedIdentity; return (0, request_context_1.runWithContext)({ session: session ?? undefined }, async () => { try { // Execute the tool (only if delegation check passed) let data = await toolHandler(request); // Add identity badge if enabled if (this.demoManager?.isIdentityBadgeEnabled()) { data = this.demoManager.addIdentityBadgeToResponse(data); } // Create response with proof const proofOptions = { ...options, ...(session && session.clientDid ? { clientDid: session.clientDid } : {}), }; const response = await (0, proof_1.createProofResponse)(request, data, cachedIdentity, session, proofOptions); // Update debug state with latest proof if (this.debugManager && response.meta?.proof) { this.debugManager.updateDebugState(response.meta.proof, session); } if (!auditInvocation || !this.config.auditTrail) return response; try { const terminal = await auditInvocation.terminal((0, mcp_i_runtime_1.terminalForToolResult)(response)); return (0, mcp_i_runtime_1.attachRuntimeAuditMetadata)(response, this.config.auditTrail.sourceId, auditInvocation.context.invocationId, auditInvocation.results(terminal)); } catch (auditError) { console.error("[MCP-I] Failed to record successful tool audit terminal", { error: auditError instanceof Error ? auditError.message : String(auditError), }); return (0, mcp_i_runtime_1.attachRuntimeAuditMetadata)(response, this.config.auditTrail.sourceId, auditInvocation.context.invocationId, auditInvocation.results(), true); } } catch (error) { if (auditInvocation) { try { await auditInvocation.terminal((0, mcp_i_runtime_1.terminalForToolError)(error)); } catch (auditError) { console.error("[MCP-I] Failed to record terminal tool audit event", { error: auditError instanceof Error ? auditError.message : String(auditError), }); } } throw error; } }); // end runWithContext } /** * Get well-known endpoint handler */ getWellKnownHandler() { if (!this.cachedIdentity) { throw new Error("Runtime not initialized - call initialize() first"); } if (!this.wellKnownManager) { throw new Error("Well-known endpoints not configured"); } return (0, well_known_1.createWellKnownHandler)(this.cachedIdentity, this.config.wellKnown); } /** * Get debug endpoint handler (development only) */ getDebugHandler(logRoot) { if (!this.cachedIdentity) { throw new Error("Runtime not initialized - call initialize() first"); } if (!this.debugManager) { throw new Error("Debug endpoint not available (not in development mode)"); } // Update debug manager with log root if provided if (logRoot) { // Store log root for receipt verification this.debugManager.logRoot = logRoot; } return this.debugManager.createDebugHandler(); } /** * Get demo manager */ getDemoManager() { return this.demoManager; } /** * Get tool protection resolver (NEW - Phase 1.5) */ getToolProtectionResolver() { return this.toolProtectionResolver; } /** * Get runtime statistics */ getStats() { return { identity: { did: this.cachedIdentity?.did, kid: this.cachedIdentity?.kid, environment: this.config.identity?.environment || "development", }, session: this.sessionManager.getStats(), audit: { enabled: this.config.auditTrail !== undefined, profile: this.config.auditTrail?.trail.capabilities?.profile ?? "AAP-0", }, runtime: { initialized: !!this.cachedIdentity, wellKnownEnabled: !!this.wellKnownManager, }, }; } /** * Cleanup resources */ async cleanup() { await this.sessionManager.cleanup(); } /** * Check runtime environment compatibility */ checkRuntimeEnvironment() { const env = this.detectRuntimeEnvironment(); // Check Node.js version if (env.isNode && env.nodeVersion) { const [major, minor] = env.nodeVersion.split(".").map(Number); if (major < 18 || (major === 18 && minor < 18)) { const error = new Error(`Unsupported Node.js version ${env.nodeVersion}. MCP-I requires Node.js ≥18.18.0`); error.code = "XMCP_I_ERUNTIME"; throw error; } } // Check ESM support if (!env.supportsESM) { const error = new Error("MCP-I requires ESM support. CommonJS-only environments are not supported."); error.code = "XMCP_I_ERUNTIME"; throw error; } console.error(`✅ Runtime environment check passed`); console.error(` Environment: ${this.describeEnvironment(env)}`); console.error(` ESM Support: ${env.supportsESM}`); if (env.nodeVersion) { console.error(` Node.js: ${env.nodeVersion}`); } } /** * Detect runtime environment */ detectRuntimeEnvironment() { const isNode = typeof process !== "undefined" && process.versions?.node; const isWorker = typeof globalThis !== "undefined" && "WorkerGlobalScope" in globalThis; const isVercelEdge = typeof globalThis !== "undefined" && "EdgeRuntime" in globalThis; const isAWSLambda = typeof process !== "undefined" && !!process.env.AWS_LAMBDA_FUNCTION_NAME; // Check ESM support // The package is CommonJS but can be imported via ESM (import from .mjs files) // ESM is supported in modern Node.js and edge runtimes const supportsESM = !!(isNode || isWorker || isVercelEdge || isAWSLambda); return { isNode: !!isNode, isWorker: !!isWorker, isVercelEdge: !!isVercelEdge, isAWSLambda: !!isAWSLambda, nodeVersion: isNode ? process.versions.node : undefined, supportsESM, }; } /** * Describe runtime environment for logging */ describeEnvironment(env) { if (env.isVercelEdge) return "Vercel Edge Runtime"; if (env.isAWSLambda) return "AWS Lambda"; if (env.isWorker) return "Web Worker"; if (env.isNode) return `Node.js ${env.nodeVersion}`; return "Unknown"; } } exports.MCPIRuntime = MCPIRuntime; /** * Create and initialize XMCP-I runtime */ async function createMCPIRuntime(config = {}) { const runtime = new MCPIRuntime(config); await runtime.initialize(); return runtime; } /** * Runtime factory for different environments */ exports.RuntimeFactory = { /** * Create runtime for development */ async forDevelopment(overrides = {}) { const config = { identity: { environment: "development", privacyMode: false, // Single public DID default devIdentityPath: ".mcpi/identity.json", ...overrides.identity, }, wellKnown: { environment: "development", baseUrl: "http://localhost:3000", ...overrides.wellKnown, }, runtime: { showVerifyLink: true, // Default true in dev ...overrides.runtime, }, demo: { identityBadge: false, // Default false, opt-in ...overrides.demo, }, session: overrides.session, auditTrail: overrides.auditTrail, }; return createMCPIRuntime(config); }, /** * Create runtime for production */ async forProduction(overrides = {}) { const config = { identity: { environment: "production", privacyMode: false, // Single public DID default }, wellKnown: { environment: "production", }, runtime: { showVerifyLink: false, }, demo: { identityBadge: false, // Disabled in production }, ...overrides, }; return createMCPIRuntime(config); }, /** * Create runtime for testing */ async forTesting(overrides = {}) { const config = { identity: { environment: "development", devIdentityPath: ".test-mcp-i/identity.json", }, runtime: { showVerifyLink: false, }, demo: { identityBadge: false, }, ...overrides, }; return createMCPIRuntime(config); }, }; /** * Error codes */ exports.RUNTIME_ERRORS = { ERUNTIME: "XMCP_I_ERUNTIME", ENOIDENTITY: "XMCP_I_ENOIDENTITY", EHANDSHAKE: "XMCP_I_EHANDSHAKE", ESESSION: "XMCP_I_ESESSION", };