UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

613 lines (600 loc) 21.3 kB
"use strict"; /** * XMCP-I Debug Tools - Development-only debug endpoints * * Provides /verify endpoint for proof inspection and debugging * in development environments only. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DebugManager = void 0; exports.createDebugEndpoint = createDebugEndpoint; /** * Debug endpoint manager */ class DebugManager { identity; environment; lastProof; lastSession; logRoot; constructor(identity, environment = "development") { this.identity = identity; this.environment = environment; } /** * Update debug state with latest proof and session */ updateDebugState(proof, session) { if (this.environment === "development") { this.lastProof = proof; this.lastSession = session; } } /** * Set log root for receipt verification */ setLogRoot(logRoot) { this.logRoot = logRoot; } /** * Generate debug page data */ async generateDebugPageData(_didDocument, agentDocument, logRoot) { if (this.environment !== "development") { throw new Error("Debug endpoint only available in development"); } const data = { identity: { did: this.identity.did, kid: this.identity.kid, didDocumentUrl: this.generateDIDDocumentUrl(this.identity.did), }, registry: { ktaUrl: this.generateKTAUrl(this.identity.did), mcpMirrorStatus: "unknown", // Would be fetched from KTA in real implementation }, capabilities: { protocol: ["handshake", "signing", "verification"], identity: ["handshake", "signing", "verification"], source: agentDocument ? "well-known" : "handshake", }, timestamp: Date.now(), environment: this.environment, }; // Add proof data if available if (this.lastProof) { data.proof = { jws: this.lastProof.jws, meta: this.lastProof.meta, canonicalHashes: { requestHash: this.lastProof.meta.requestHash, responseHash: this.lastProof.meta.responseHash, }, }; // Perform local verification data.verification = await this.performLocalVerification(this.lastProof, this.lastSession); } // Add log root for receipt verification if (logRoot) { data.logRoot = logRoot; } return data; } /** * Create debug endpoint handler */ createDebugHandler() { return async (_request) => { if (this.environment !== "development") { return new Response("Debug endpoint not available in production", { status: 404, }); } try { const debugData = await this.generateDebugPageData(undefined, undefined, this.logRoot); const html = this.generateDebugHTML(debugData); return new Response(html, { headers: { "Content-Type": "text/html", "Cache-Control": "no-store", }, }); } catch (error) { console.error("Debug endpoint error:", error); return new Response("Internal server error", { status: 500 }); } }; } /** * Perform local verification of proof */ async performLocalVerification(proof, session) { const result = { success: false, signature: { valid: false, algorithm: "EdDSA", kid: proof.meta.kid, }, proof: { valid: false, timestamp: { valid: false, skew: 0, }, hashes: { requestValid: false, responseValid: false, }, }, session: { valid: false, expired: false, ttl: 0, }, errors: [], }; try { // Validate timestamp const now = Math.floor(Date.now() / 1000); const skew = Math.abs(now - proof.meta.ts); result.proof.timestamp.skew = skew; result.proof.timestamp.valid = skew <= 120; // Default 120s tolerance if (!result.proof.timestamp.valid) { result.errors?.push(`Timestamp skew too large: ${skew}s (max 120s). Check NTP sync.`); result.proof.timestamp.remediation = "Check NTP sync; adjust XMCP_I_TS_SKEW_SEC"; } // Validate hash format result.proof.hashes.requestValid = /^sha256:[a-f0-9]{64}$/.test(proof.meta.requestHash); result.proof.hashes.responseValid = /^sha256:[a-f0-9]{64}$/.test(proof.meta.responseHash); if (!result.proof.hashes.requestValid) { result.errors?.push("Invalid request hash format"); } if (!result.proof.hashes.responseValid) { result.errors?.push("Invalid response hash format"); } // Validate session if available if (session) { const sessionAge = now - Math.floor(session.createdAt / 1000); const sessionTtl = session.ttlMinutes || 30; result.session.ttl = sessionTtl * 60 - sessionAge; result.session.expired = result.session.ttl <= 0; result.session.valid = !result.session.expired; if (result.session.expired) { result.errors?.push("Session expired"); } } // Overall validation result.proof.valid = result.proof.timestamp.valid && result.proof.hashes.requestValid && result.proof.hashes.responseValid; result.success = result.proof.valid && result.session.valid && !result.errors?.length; // Note: In a real implementation, we would verify the JWS signature // using the public key from the DID document result.signature.valid = true; // Placeholder for actual signature verification } catch (error) { result.errors?.push(`Verification error: ${error}`); } return result; } /** * Generate DID document URL */ generateDIDDocumentUrl(did) { if (did.startsWith("did:web:")) { const domain = did.replace("did:web:", "").replace(/:/g, "/"); return `https://${domain}/.well-known/did.json`; } return `${did}/.well-known/did.json`; } /** * Generate KTA URL */ generateKTAUrl(did) { // Extract agent identifier from DID for KTA URL const agentId = did.split(":").pop() || "unknown"; return `https://knowthat.ai/agents/${agentId}`; } /** * Generate debug HTML page */ generateDebugHTML(data) { return `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>XMCP-I Debug - Proof Verification</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; line-height: 1.6; margin: 0; padding: 20px; background: #f5f5f5; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); overflow: hidden; } .header { background: #2563eb; color: white; padding: 20px; } .header h1 { margin: 0; font-size: 24px; } .header .subtitle { opacity: 0.9; margin-top: 5px; } .section { padding: 20px; border-bottom: 1px solid #e5e7eb; } .section:last-child { border-bottom: none; } .section h2 { margin: 0 0 15px 0; color: #1f2937; font-size: 18px; } .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; } .card { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 15px; } .card h3 { margin: 0 0 10px 0; color: #374151; font-size: 16px; } .field { margin-bottom: 10px; } .field label { display: block; font-weight: 600; color: #4b5563; margin-bottom: 2px; font-size: 14px; } .field .value { font-family: 'SF Mono', Monaco, monospace; font-size: 13px; background: white; border: 1px solid #d1d5db; border-radius: 4px; padding: 8px; word-break: break-all; } .status { display: inline-block; padding: 2px 8px; border-radius: 12px; font-size: 12px; font-weight: 600; } .status.success { background: #dcfce7; color: #166534; } .status.error { background: #fef2f2; color: #dc2626; } .status.warning { background: #fef3c7; color: #d97706; } .status.unknown { background: #f3f4f6; color: #6b7280; } .code-block { background: #1f2937; color: #f9fafb; padding: 15px; border-radius: 6px; font-family: 'SF Mono', Monaco, monospace; font-size: 13px; overflow-x: auto; white-space: pre-wrap; word-break: break-all; } .warning-banner { background: #fef3c7; border: 1px solid #f59e0b; border-radius: 6px; padding: 15px; margin-bottom: 20px; } .warning-banner strong { color: #92400e; } .link { color: #2563eb; text-decoration: none; } .link:hover { text-decoration: underline; } .timestamp { color: #6b7280; font-size: 12px; margin-top: 20px; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🔍 XMCP-I Debug Console</h1> <div class="subtitle">Proof Verification & Identity Inspection</div> </div> <div class="warning-banner"> <strong>Development Only:</strong> This debug endpoint is only available in development mode and does not expose payload bodies or secrets. </div> <div class="section"> <h2>Agent Identity</h2> <div class="grid"> <div class="card"> <h3>Identity Information</h3> <div class="field"> <label>DID</label> <div class="value">${data.identity.did}</div> </div> <div class="field"> <label>Key ID</label> <div class="value">${data.identity.kid}</div> </div> <div class="field"> <label>DID Document</label> <div class="value"> <a href="${data.identity.didDocumentUrl}" class="link" target="_blank"> ${data.identity.didDocumentUrl} </a> </div> </div> </div> <div class="card"> <h3>Registry Status</h3> <div class="field"> <label>Know-That-AI</label> <div class="value"> <a href="${data.registry.ktaUrl}" class="link" target="_blank"> ${data.registry.ktaUrl} </a> </div> </div> <div class="field"> <label>MCP Mirror Status</label> <div class="value"> <span class="status ${data.registry.mcpMirrorStatus}"> ${data.registry.mcpMirrorStatus} </span> </div> </div> ${data.logRoot ? ` <div class="field"> <label>Log Root (Receipt Verification)</label> <div class="value">${data.logRoot}</div> </div> ` : ""} </div> </div> </div> <div class="section"> <h2>Protocol Capabilities</h2> <div class="card"> <h3>Advertised Capabilities</h3> <div class="field"> <label>MCP-I Features</label> <div class="value">${JSON.stringify(data.capabilities.identity)}</div> </div> <div class="field"> <label>Source</label> <div class="value">${data.capabilities.source}</div> </div> </div> </div> ${data.proof ? ` <div class="section"> <h2>Latest Proof</h2> <div class="grid"> <div class="card"> <h3>Proof Metadata</h3> <div class="field"> <label>Session ID</label> <div class="value">${data.proof.meta.sessionId}</div> </div> <div class="field"> <label>Audience</label> <div class="value">${data.proof.meta.audience}</div> </div> <div class="field"> <label>Timestamp</label> <div class="value">${new Date(data.proof.meta.ts * 1000).toISOString()}</div> </div> <div class="field"> <label>Nonce</label> <div class="value">${data.proof.meta.nonce}</div> </div> ${data.proof.meta.scopeId ? ` <div class="field"> <label>Scope ID</label> <div class="value">${data.proof.meta.scopeId}</div> </div> ` : ""} ${data.proof.meta.delegationRef ? ` <div class="field"> <label>Delegation Ref</label> <div class="value">${data.proof.meta.delegationRef}</div> </div> ` : ""} </div> <div class="card"> <h3>Canonical Hashes</h3> <div class="field"> <label>Request Hash</label> <div class="value">${data.proof.canonicalHashes.requestHash}</div> </div> <div class="field"> <label>Response Hash</label> <div class="value">${data.proof.canonicalHashes.responseHash}</div> </div> </div> </div> <div class="card" style="margin-top: 20px;"> <h3>Detached JWS</h3> <div class="code-block">${data.proof.jws}</div> </div> </div> ` : ` <div class="section"> <h2>Latest Proof</h2> <div class="card"> <p style="color: #6b7280; font-style: italic;"> No proof data available. Make a tool call to see proof verification details. </p> </div> </div> `} ${data.verification ? ` <div class="section"> <h2>Local Verification Result</h2> <div class="grid"> <div class="card"> <h3>Overall Status</h3> <div class="field"> <label>Verification</label> <div class="value"> <span class="status ${data.verification.success ? "success" : "error"}"> ${data.verification.success ? "VALID" : "INVALID"} </span> </div> </div> ${data.verification.errors?.length ? ` <div class="field"> <label>Errors</label> <div class="value" style="color: #dc2626;"> ${data.verification.errors.join("; ")} </div> </div> ` : ""} </div> <div class="card"> <h3>Signature Check</h3> <div class="field"> <label>Valid</label> <div class="value"> <span class="status ${data.verification.signature.valid ? "success" : "error"}"> ${data.verification.signature.valid ? "VALID" : "INVALID"} </span> </div> </div> <div class="field"> <label>Algorithm</label> <div class="value">${data.verification.signature.algorithm}</div> </div> </div> <div class="card"> <h3>Timestamp Check</h3> <div class="field"> <label>Valid</label> <div class="value"> <span class="status ${data.verification.proof.timestamp.valid ? "success" : "error"}"> ${data.verification.proof.timestamp.valid ? "VALID" : "INVALID"} </span> </div> </div> <div class="field"> <label>Clock Skew</label> <div class="value">${data.verification.proof.timestamp.skew}s</div> </div> ${data.verification.proof.timestamp.remediation ? ` <div class="field"> <label>Remediation</label> <div class="value" style="color: #d97706;"> ${data.verification.proof.timestamp.remediation} </div> </div> ` : ""} </div> <div class="card"> <h3>Session Check</h3> <div class="field"> <label>Valid</label> <div class="value"> <span class="status ${data.verification.session.valid ? "success" : "error"}"> ${data.verification.session.valid ? "VALID" : "INVALID"} </span> </div> </div> <div class="field"> <label>TTL Remaining</label> <div class="value">${Math.max(0, data.verification.session.ttl)}s</div> </div> </div> </div> </div> ` : ""} <div class="timestamp"> Generated at ${new Date(data.timestamp).toISOString()} • Environment: ${data.environment} </div> </div> </body> </html>`; } } exports.DebugManager = DebugManager; /** * Create debug endpoint handler for development */ function createDebugEndpoint(identity, environment = "development") { return new DebugManager(identity, environment); }