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