@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
473 lines (472 loc) • 19.2 kB
JavaScript
"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",
};