UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

473 lines (472 loc) 19.2 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 audit_1 = require("./audit"); 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 tool_protection_1 = require("./tool-protection"); const tool_protection_registry_1 = require("./tool-protection-registry"); /** * XMCP-I Runtime class */ /** * @deprecated Use MCPINodeRuntimeWrapper instead. * This class is maintained for backward compatibility only. */ class MCPIRuntime { identityManager; sessionManager; auditLogger; 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 audit logger this.auditLogger = new audit_1.AuditLogger(config.audit); // Initialize resume token store (NEW - Phase 1) // Use in-memory store by default (sufficient for most use cases) this.resumeTokenStore = new auth_handshake_1.MemoryResumeTokenStore(config.delegation?.authorization?.resumeTokenTtl || 600_000); // 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", }); 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, log the delegation ID for audit trail if (verifyResult.delegation) { options.delegationRef = verifyResult.delegation.id; } } 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, this.cachedIdentity, session, proofOptions); // Update debug state with latest proof if (this.debugManager && response.meta?.proof) { this.debugManager.updateDebugState(response.meta.proof, session); } // Log audit record (first call per session) const auditContext = { identity: this.cachedIdentity, session, requestHash: response.meta.proof.meta.requestHash, responseHash: response.meta.proof.meta.responseHash, verified: "yes", scopeId: options.scopeId, }; await this.auditLogger.logAuditRecord(auditContext); return response; } catch (error) { // Log failed audit record const auditContext = { identity: this.cachedIdentity, session, requestHash: "sha256:error", responseHash: "sha256:error", verified: "no", scopeId: options.scopeId, }; await this.auditLogger.logAuditRecord(auditContext); throw error; } } /** * 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: this.auditLogger.getStats(), runtime: { initialized: !!this.cachedIdentity, wellKnownEnabled: !!this.wellKnownManager, }, }; } /** * Cleanup resources */ async cleanup() { await this.sessionManager.cleanup(); this.auditLogger.clearSessionLog(); } /** * 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, audit: overrides.audit, }; 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", }, audit: { enabled: false, // Disable audit logging in tests }, 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", };