@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
417 lines (416 loc) • 15.6 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CoreVerifier = void 0;
exports.verifyWorker = verifyWorker;
exports.verifyExpress = verifyExpress;
const verifier_1 = require("@kya-os/contracts/verifier");
const merkle_verifier_1 = require("../storage/merkle-verifier");
const delegation_1 = require("../storage/delegation");
const mcp_i_core_1 = require("@kya-os/mcp-i-core");
/**
* Core verifier implementation
*/
class CoreVerifier {
config;
receiptVerifier;
delegationManager;
cryptoService;
constructor(config = {}) {
this.config = config;
this.receiptVerifier = (0, merkle_verifier_1.createReceiptVerifier)({
enabled: config.receiptVerification !== false,
ktaBaseUrl: config.ktaBaseUrl,
allowMockData: config.allowMockData,
});
this.delegationManager = (0, delegation_1.createDelegationManager)({
ktaBaseURL: config.ktaBaseUrl || "https://knowthat.ai",
});
// Initialize CryptoService if cryptoProvider is provided
if (config.cryptoProvider) {
this.cryptoService = new mcp_i_core_1.CryptoService(config.cryptoProvider);
}
}
/**
* Verify proof with optional receipt checking
*/
async verify(context) {
try {
// 1. Verify proof signature (placeholder - would use actual crypto verification)
const signatureValid = await this.verifySignature(context.proof, context.detachedProof);
if (!signatureValid) {
return {
success: false,
error: {
code: "XMCP_I_EBADPROOF",
message: "Invalid proof signature",
httpStatus: 403,
},
};
}
// 2. Check delegation if present
if (context.proof.delegationRef &&
this.config.delegationChecking !== false) {
const delegationValid = await this.verifyDelegation(context.proof.delegationRef);
if (!delegationValid) {
return {
success: false,
error: {
code: "XMCP_I_EBADPROOF",
message: "Delegation revoked or invalid",
httpStatus: 403,
},
};
}
}
// 3. Verify receipt if present and policy requires it
if (context.receipt && this.shouldVerifyReceipt()) {
const receiptResult = await this.receiptVerifier.verify(context.receipt);
if (!receiptResult.valid) {
return {
success: false,
error: {
code: "XMCP_I_ERECEIPT",
message: `Receipt verification failed: ${receiptResult.error}`,
httpStatus: 403,
},
};
}
}
// 4. Generate trusted headers
const headers = this.generateHeaders(context.proof);
return {
success: true,
headers,
};
}
catch (error) {
return {
success: false,
error: {
code: "XMCP_I_EVERIFY",
message: error instanceof Error ? error.message : "Verification failed",
httpStatus: 500,
},
};
}
}
/**
* Verify proof signature
*
* Note: Full signature verification requires DetachedProof with JWS.
* If only ProofMeta is available, performs structure validation only.
* To enable full verification, provide detachedProof in VerifierContext.
*/
async verifySignature(proof, detachedProof) {
// Basic structure validation
const hasRequiredFields = !!(proof.did &&
proof.kid &&
proof.ts &&
proof.nonce &&
proof.audience &&
proof.sessionId &&
proof.requestHash &&
proof.responseHash);
if (!hasRequiredFields) {
return false;
}
// If we have full DetachedProof with JWS and CryptoService, verify signature
if (detachedProof?.jws && this.cryptoService) {
try {
// Fetch public key from DID document
// Note: This is a simplified implementation - production should use proper DID resolver
const publicKeyJwk = await this.fetchPublicKeyFromDID(proof.did, proof.kid);
if (!publicKeyJwk) {
console.warn("[CoreVerifier] Could not resolve public key from DID, skipping signature verification");
return true; // Fall back to structure validation only
}
// Verify JWS signature
return await this.cryptoService.verifyJWS(detachedProof.jws, publicKeyJwk, {
expectedKid: proof.kid,
alg: "EdDSA",
});
}
catch (error) {
console.error("[CoreVerifier] Signature verification error:", error);
return false;
}
}
// Fallback: structure validation only (when JWS not available)
return true;
}
/**
* Fetch public key from DID document
*
* Note: This is a simplified implementation. Production code should use
* a proper DID resolver that supports multiple DID methods (did:key, did:web, etc.)
*/
async fetchPublicKeyFromDID(did, kid) {
// For now, only support did:web resolution
// TODO: Add support for did:key and other DID methods
if (!did.startsWith("did:web:")) {
console.warn(`[CoreVerifier] Unsupported DID method: ${did}`);
return null;
}
try {
const domain = did.replace("did:web:", "").replace(/:/g, "/");
const didDocUrl = `https://${domain}/.well-known/did.json`;
const response = await fetch(didDocUrl);
if (!response.ok) {
console.warn(`[CoreVerifier] Failed to fetch DID document: ${response.status}`);
return null;
}
const didDoc = await response.json();
// Find verification method
const verificationMethod = didDoc.verificationMethod?.find((vm) => vm.id === `#${kid}` || vm.id === `${did}#${kid}` || vm.id === kid);
if (!verificationMethod?.publicKeyJwk) {
console.warn(`[CoreVerifier] Verification method ${kid} not found in DID document`);
return null;
}
// Validate JWK format
const jwk = verificationMethod.publicKeyJwk;
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519") {
console.warn(`[CoreVerifier] Invalid JWK format: expected Ed25519, got ${jwk.kty}/${jwk.crv}`);
return null;
}
return jwk;
}
catch (error) {
console.error("[CoreVerifier] DID resolution error:", error);
return null;
}
}
/**
* Verify delegation status
*/
async verifyDelegation(delegationRef) {
try {
return await this.delegationManager.isActive(delegationRef);
}
catch (error) {
console.warn("Delegation verification failed:", error);
return false;
}
}
/**
* Check if receipt verification should be performed
*/
shouldVerifyReceipt() {
const policy = this.config.receiptPolicy || "optional";
return (policy === "required" ||
(policy === "optional" && this.config.receiptVerification !== false));
}
/**
* Generate trusted headers for successful verification
*/
generateHeaders(proof) {
const headers = {
[verifier_1.AGENT_HEADERS.DID]: proof.did,
[verifier_1.AGENT_HEADERS.KEY_ID]: proof.kid,
[verifier_1.AGENT_HEADERS.SESSION]: proof.sessionId,
[verifier_1.AGENT_HEADERS.CONFIDENCE]: "verified",
[verifier_1.AGENT_HEADERS.VERIFIED_AT]: Math.floor(Date.now() / 1000).toString(),
};
if (proof.scopeId) {
headers[verifier_1.AGENT_HEADERS.SCOPES] = proof.scopeId;
}
if (proof.delegationRef) {
headers[verifier_1.AGENT_HEADERS.DELEGATION_REF] = proof.delegationRef;
}
// Add registry URL for traceability
const ktaUrl = this.config.ktaBaseUrl || "https://knowthat.ai";
headers[verifier_1.AGENT_HEADERS.REGISTRY] =
`${ktaUrl}/agents/${encodeURIComponent(proof.did)}`;
return headers;
}
}
exports.CoreVerifier = CoreVerifier;
/**
* Cloudflare Worker verifier
*/
async function verifyWorker(request, config) {
try {
// Extract proof from request headers or body
const proof = await parseProofFromHeaders(request);
if (!proof) {
return {
success: false,
error: {
code: "XMCP_I_ENOIDENTITY",
message: "No proof found in request",
httpStatus: 401,
},
};
}
// Extract receipt if present
const receipt = await parseReceiptFromHeaders(request);
const verifier = new CoreVerifier(config);
return await verifier.verify({ proof, receipt: receipt || undefined });
}
catch (error) {
return {
success: false,
error: {
code: "XMCP_I_EVERIFY",
message: error instanceof Error ? error.message : "Verification failed",
httpStatus: 500,
},
};
}
}
/**
* Express verifier middleware
*/
function verifyExpress(config) {
return async (req, res, next) => {
try {
// Extract proof from request
const proof = await parseProofFromExpressHeaders(req);
if (!proof) {
return res.status(401).json({
code: "XMCP_I_ENOIDENTITY",
message: "No proof found in request",
});
}
// Extract receipt if present
const receipt = await parseReceiptFromExpressHeaders(req);
const verifier = new CoreVerifier(config);
const result = await verifier.verify({
proof,
receipt: receipt || undefined,
});
if (!result.success) {
return res.status(result.error.httpStatus).json({
code: result.error.code,
message: result.error.message,
details: result.error.details,
});
}
// Set headers and context
if (result.headers) {
Object.entries(result.headers).forEach(([key, value]) => {
res.setHeader(key, value);
});
// Set ctx.agent for MCP recipients
req.ctx = req.ctx || {};
req.ctx.agent = {
did: result.headers[verifier_1.AGENT_HEADERS.DID],
kid: result.headers[verifier_1.AGENT_HEADERS.KEY_ID],
session: result.headers[verifier_1.AGENT_HEADERS.SESSION],
scopes: result.headers[verifier_1.AGENT_HEADERS.SCOPES]?.split(",") || [],
delegationRef: result.headers[verifier_1.AGENT_HEADERS.DELEGATION_REF],
confidence: result.headers[verifier_1.AGENT_HEADERS.CONFIDENCE],
verifiedAt: parseInt(result.headers[verifier_1.AGENT_HEADERS.VERIFIED_AT]),
};
}
next();
}
catch (error) {
res.status(500).json({
code: "XMCP_I_EVERIFY",
message: error instanceof Error ? error.message : "Verification failed",
});
}
};
}
/**
* Helper: Extract header value from different header types
*/
function getHeaderValue(headers, name) {
if (headers instanceof Headers) {
return headers.get(name);
}
const header = headers[name.toLowerCase()];
if (!header)
return null;
return Array.isArray(header) ? header[0] : header;
}
/**
* Parse proof from request headers
*
* This function extracts ProofMeta from request headers.
* ProofMeta type is defined in @kya-os/contracts/proof.
*
* @param request - Fetch API Request or Express Request
* @returns ProofMeta (from @kya-os/contracts/proof) if found, null otherwise
*/
async function parseProofFromHeaders(request) {
// TODO: Implement actual proof extraction from request
// This would typically look for proof in headers or request body
// Placeholder implementation
// Handle both Fetch API Request and Node.js IncomingMessage
const proofHeader = getHeaderValue(request.headers, "X-XMCP-I-Proof");
if (!proofHeader) {
return null;
}
try {
return JSON.parse(proofHeader);
}
catch {
return null;
}
}
/**
* Parse receipt from request headers
*
* This function extracts Receipt from request headers.
* Receipt type is defined in @kya-os/contracts/registry.
*
* @param request - Fetch API Request or Express Request
* @returns Receipt (from @kya-os/contracts/registry) if found, null otherwise
*/
async function parseReceiptFromHeaders(request) {
// TODO: Implement actual receipt extraction from request
// This would typically look for receipt reference in headers
// Handle both Fetch API Request and Node.js IncomingMessage
const receiptRef = getHeaderValue(request.headers, "X-XMCP-I-Receipt-Ref");
if (!receiptRef) {
return null;
}
// TODO: Fetch receipt by reference from storage
return null;
}
/**
* Parse proof from Express request headers
*
* This function extracts ProofMeta from Express request headers.
* ProofMeta type is defined in @kya-os/contracts/proof.
*
* @param req - Express Request
* @returns ProofMeta (from @kya-os/contracts/proof) if found, null otherwise
*/
async function parseProofFromExpressHeaders(req) {
// TODO: Implement actual proof extraction from Express request
// This would typically look for proof in headers or request body
const proofHeader = req.headers["x-xmcp-i-proof"];
if (!proofHeader) {
return null;
}
try {
const headerValue = Array.isArray(proofHeader)
? proofHeader[0]
: proofHeader;
return JSON.parse(headerValue);
}
catch {
return null;
}
}
/**
* Parse receipt from Express request headers
*
* This function extracts Receipt from Express request headers.
* Receipt type is defined in @kya-os/contracts/registry.
*
* @param req - Express Request
* @returns Receipt (from @kya-os/contracts/registry) if found, null otherwise
*/
async function parseReceiptFromExpressHeaders(req) {
// TODO: Implement actual receipt extraction from Express request
// This would typically look for receipt reference in headers
const receiptRef = req.headers["x-xmcp-i-receipt-ref"];
if (!receiptRef) {
return null;
}
// TODO: Fetch receipt by reference from storage
return null;
}