UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

245 lines (244 loc) 8.26 kB
"use strict"; /** * Well-Known Endpoints for XMCP-I Runtime * * Handles /.well-known/did.json and /.well-known/agent.json endpoints * according to requirements 7.1, 7.2, 7.3, 7.4, 7.5. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.WellKnownManager = void 0; exports.createWellKnownHandler = createWellKnownHandler; exports.validateDIDDocument = validateDIDDocument; exports.validateAgentDocument = validateAgentDocument; exports.extractDIDFromPath = extractDIDFromPath; // Load base-x synchronously using require (mcp-i is CommonJS) // eslint-disable-next-line @typescript-eslint/no-var-requires const baseX = require("base-x"); const base58 = baseX.default || baseX; const base58Encoder = base58('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'); /** * Well-known endpoints manager */ class WellKnownManager { identity; config; constructor(identity, config) { this.identity = identity; this.config = config; } /** * Generate DID document for /.well-known/did.json * Requirements: 7.1, 7.5 */ generateDIDDocument() { const kid = `#${this.identity.kid}`; // Convert base64 public key to multibase format const publicKeyMultibase = this.encodePublicKeyMultibase(this.identity.publicKey); const didDocument = { "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/ed25519-2020/v1", ], id: this.identity.did, verificationMethod: [ { id: kid, type: "Ed25519VerificationKey2020", controller: this.identity.did, publicKeyMultibase, }, ], authentication: [kid], assertionMethod: [kid], }; return didDocument; } /** * Generate agent document for /.well-known/agent.json * Requirements: 7.2, 7.3 */ generateAgentDocument() { const agentDocument = { id: this.identity.did, capabilities: { "mcp-i": ["handshake", "signing", "verification"], // Exact capability triplet }, }; // Add registry URLs if configured if (this.config.registryUrls) { agentDocument.registry = { ...this.config.registryUrls }; } // Add metadata if configured if (this.config.agentMetadata) { agentDocument.metadata = { ...this.config.agentMetadata }; } return agentDocument; } /** * Get HTTP headers for DID document * Requirements: 7.4, 7.5 */ getDIDDocumentHeaders() { const headers = { "Content-Type": "application/did+json", }; // Add caching headers based on environment if (this.config.environment === "production") { headers["Cache-Control"] = "public, max-age=300"; // 5 minutes } else { headers["Cache-Control"] = "no-store"; } return headers; } /** * Get HTTP headers for agent document * Requirements: 7.2, 7.5 */ getAgentDocumentHeaders() { const headers = { "Content-Type": "application/json", }; // Always no-store for agent document in dev, public cache in prod if (this.config.environment === "production") { headers["Cache-Control"] = "public, max-age=300"; // 5 minutes } else { headers["Cache-Control"] = "no-store"; } return headers; } /** * Encode public key as multibase (base58btc with 'z' prefix for Ed25519) */ encodePublicKeyMultibase(base64PublicKey) { // For Ed25519, we use base58btc encoding with 'z' prefix const publicKeyBytes = Buffer.from(base64PublicKey, "base64"); // Ed25519 public key prefix (0xed01) + key bytes const prefixedKey = Buffer.concat([ Buffer.from([0xed, 0x01]), // Ed25519 multicodec prefix publicKeyBytes, ]); // Convert to base58btc using base-x library const base58Encoded = base58Encoder.encode(prefixedKey); return `z${base58Encoded}`; // 'z' prefix indicates base58btc } /** * Update configuration */ updateConfig(config) { this.config = { ...this.config, ...config }; } /** * Get current configuration */ getConfig() { return { ...this.config }; } } exports.WellKnownManager = WellKnownManager; /** * Create well-known endpoint handler */ function createWellKnownHandler(identity, config) { const manager = new WellKnownManager(identity, config); return { handleDIDDocument() { try { const didDocument = manager.generateDIDDocument(); const headers = manager.getDIDDocumentHeaders(); return { status: 200, headers, body: JSON.stringify(didDocument, null, 2), }; } catch (error) { return { status: 500, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Failed to generate DID document", message: error instanceof Error ? error.message : "Unknown error", }), }; } }, handleAgentDocument() { try { const agentDocument = manager.generateAgentDocument(); const headers = manager.getAgentDocumentHeaders(); return { status: 200, headers, body: JSON.stringify(agentDocument, null, 2), }; } catch (error) { return { status: 500, headers: { "Content-Type": "application/json" }, body: JSON.stringify({ error: "Failed to generate agent document", message: error instanceof Error ? error.message : "Unknown error", }), }; } }, }; } /** * Utility functions */ /** * Validate DID document structure */ function validateDIDDocument(doc) { return (typeof doc === "object" && doc !== null && Array.isArray(doc["@context"]) && typeof doc.id === "string" && doc.id.startsWith("did:") && Array.isArray(doc.verificationMethod) && Array.isArray(doc.authentication) && Array.isArray(doc.assertionMethod)); } /** * Validate agent document structure */ function validateAgentDocument(doc) { return (typeof doc === "object" && doc !== null && typeof doc.id === "string" && doc.id.startsWith("did:") && typeof doc.capabilities === "object" && Array.isArray(doc.capabilities["mcp-i"]) && doc.capabilities["mcp-i"].length === 3 && doc.capabilities["mcp-i"].includes("handshake") && doc.capabilities["mcp-i"].includes("signing") && doc.capabilities["mcp-i"].includes("verification")); } /** * Extract DID from URL path (for did:web resolution) */ function extractDIDFromPath(path, baseUrl) { try { // For did:web, the DID is constructed from the domain and path // Example: /.well-known/did.json -> did:web:example.com // Example: /agents/my-agent/.well-known/did.json -> did:web:example.com:agents:my-agent const url = new URL(baseUrl); const domain = url.hostname; // Remove /.well-known/did.json from path const cleanPath = path.replace("/.well-known/did.json", ""); if (cleanPath === "") { return `did:web:${domain}`; } // Convert path segments to DID path components const pathComponents = cleanPath.split("/").filter(Boolean); const didPath = pathComponents.join(":"); return `did:web:${domain}:${didPath}`; } catch { return null; } }