UNPKG

@kya-os/mcp-i

Version:

The TypeScript MCP framework with identity features built-in

369 lines (368 loc) 13.3 kB
"use strict"; /** * Node.js Provider Implementations * * Wraps existing Node.js implementations to work with the provider-based core. * This maintains backward compatibility while enabling the new architecture. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileIdentityProvider = exports.FileStorageProvider = exports.NodeFetchProvider = exports.NodeClockProvider = exports.NodeCryptoProvider = void 0; exports.getNonceCacheProvider = getNonceCacheProvider; const crypto = __importStar(require("crypto")); const fs = __importStar(require("fs")); const path = __importStar(require("path")); const axios_1 = __importDefault(require("axios")); const mcp_i_core_1 = require("@kya-os/mcp-i-core"); /** * Node.js Crypto Provider using built-in crypto module */ class NodeCryptoProvider extends mcp_i_core_1.CryptoProvider { async sign(data, privateKeyBase64) { const privateKey = Buffer.from(privateKeyBase64, 'base64'); // Handle both raw 32-byte and full 64-byte Ed25519 keys const keyBytes = privateKey.length === 64 ? privateKey.subarray(0, 32) : privateKey; // Wrap in PKCS8 format for Node.js crypto const pkcs8 = Buffer.concat([ Buffer.from('302e020100300506032b657004220420', 'hex'), keyBytes ]); const keyObject = crypto.createPrivateKey({ key: pkcs8, format: 'der', type: 'pkcs8' }); const signature = crypto.sign(null, Buffer.from(data), keyObject); return new Uint8Array(signature); } async verify(data, signature, publicKeyBase64) { try { const publicKey = Buffer.from(publicKeyBase64, 'base64'); // Wrap in SPKI format for Node.js crypto const spki = Buffer.concat([ Buffer.from('302a300506032b6570032100', 'hex'), publicKey ]); const keyObject = crypto.createPublicKey({ key: spki, format: 'der', type: 'spki' }); return crypto.verify(null, Buffer.from(data), keyObject, Buffer.from(signature)); } catch { return false; } } async generateKeyPair() { const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', { publicKeyEncoding: { type: 'spki', format: 'der' }, privateKeyEncoding: { type: 'pkcs8', format: 'der' } }); // Extract raw keys from DER encoding const rawPrivate = privateKey.subarray(16, 48); const rawPublic = publicKey.subarray(12, 44); return { privateKey: rawPrivate.toString('base64'), publicKey: rawPublic.toString('base64') }; } async hash(data) { const hash = crypto.createHash('sha256').update(Buffer.from(data)).digest(); return new Uint8Array(hash); } async randomBytes(length) { return new Uint8Array(crypto.randomBytes(length)); } } exports.NodeCryptoProvider = NodeCryptoProvider; /** * Node.js Clock Provider */ class NodeClockProvider extends mcp_i_core_1.ClockProvider { now() { return Date.now(); } isWithinSkew(timestamp, skewSeconds) { const now = this.now(); const skewMs = skewSeconds * 1000; return Math.abs(now - timestamp) <= skewMs; } hasExpired(expiresAt) { return this.now() > expiresAt; } calculateExpiry(ttlSeconds) { return this.now() + (ttlSeconds * 1000); } format(timestamp) { return new Date(timestamp).toISOString(); } } exports.NodeClockProvider = NodeClockProvider; /** * Node.js Fetch Provider using axios */ class NodeFetchProvider extends mcp_i_core_1.FetchProvider { async resolveDID(did) { if (did.startsWith('did:key:')) { const publicKeyMultibase = did.slice('did:key:'.length); return { '@context': ['https://www.w3.org/ns/did/v1'], id: did, verificationMethod: [{ id: `${did}#key-1`, type: 'Ed25519VerificationKey2020', controller: did, publicKeyMultibase }], authentication: [`${did}#key-1`], assertionMethod: [`${did}#key-1`] }; } if (did.startsWith('did:web:')) { const domain = did.slice('did:web:'.length).replace(/:/g, '/'); const url = `https://${domain}/.well-known/did.json`; const response = await axios_1.default.get(url); return response.data; } throw new Error(`Unsupported DID method: ${did}`); } async fetchStatusList(url) { const response = await axios_1.default.get(url); return response.data; } async fetchDelegationChain(_id) { // Would fetch from a delegation registry return []; } async fetch(url, options) { const response = await (0, axios_1.default)({ url, method: options?.method || 'GET', headers: options?.headers || {}, data: options?.body }); // Create a Response-like object return { ok: response.status >= 200 && response.status < 300, status: response.status, statusText: response.statusText, headers: new Headers(response.headers), json: async () => response.data, text: async () => JSON.stringify(response.data), blob: async () => new Blob([JSON.stringify(response.data)]), arrayBuffer: async () => { const encoder = new TextEncoder(); return encoder.encode(JSON.stringify(response.data)).buffer; }, bytes: async () => { const encoder = new TextEncoder(); return encoder.encode(JSON.stringify(response.data)); }, formData: async () => { throw new Error('Not implemented'); }, clone: () => { throw new Error('Not implemented'); }, body: null, bodyUsed: false, redirected: false, type: 'basic', url }; } } exports.NodeFetchProvider = NodeFetchProvider; /** * File System Storage Provider */ class FileStorageProvider extends mcp_i_core_1.StorageProvider { basePath; constructor(basePath) { super(); this.basePath = basePath; fs.mkdirSync(basePath, { recursive: true }); } async get(key) { try { const filePath = path.join(this.basePath, key); return fs.readFileSync(filePath, 'utf-8'); } catch { return null; } } async set(key, value) { const filePath = path.join(this.basePath, key); const dir = path.dirname(filePath); fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(filePath, value); } async delete(key) { try { const filePath = path.join(this.basePath, key); fs.unlinkSync(filePath); } catch { // Ignore if file doesn't exist } } async exists(key) { const filePath = path.join(this.basePath, key); return fs.existsSync(filePath); } async list(prefix) { const files = []; const basePath = this.basePath; function walk(dir) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { walk(fullPath); } else { const relativePath = path.relative(basePath, fullPath); if (!prefix || relativePath.startsWith(prefix)) { files.push(relativePath); } } } } walk(this.basePath); return files; } } exports.FileStorageProvider = FileStorageProvider; /** * File System Identity Provider */ class FileIdentityProvider extends mcp_i_core_1.IdentityProvider { basePath; cryptoProvider; identityFile; constructor(basePath, cryptoProvider) { super(); this.basePath = basePath; this.cryptoProvider = cryptoProvider; this.identityFile = path.join(basePath, 'identity.json'); fs.mkdirSync(basePath, { recursive: true }); } async getIdentity() { // Try to load from environment first if (process.env.MCP_IDENTITY_PRIVATE_KEY && process.env.MCP_IDENTITY_PUBLIC_KEY && process.env.MCP_IDENTITY_AGENT_DID) { return { did: process.env.MCP_IDENTITY_AGENT_DID, kid: `${process.env.MCP_IDENTITY_AGENT_DID}#key-1`, privateKey: process.env.MCP_IDENTITY_PRIVATE_KEY, publicKey: process.env.MCP_IDENTITY_PUBLIC_KEY, createdAt: new Date().toISOString(), type: 'production' }; } // Try to load from file if (fs.existsSync(this.identityFile)) { const data = fs.readFileSync(this.identityFile, 'utf-8'); return JSON.parse(data); } // Generate new identity const keyPair = await this.cryptoProvider.generateKeyPair(); const did = this.generateDIDFromPublicKey(keyPair.publicKey); const identity = { did, kid: `${did}#key-1`, privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, createdAt: new Date().toISOString(), type: 'development' }; await this.saveIdentity(identity); return identity; } async saveIdentity(identity) { fs.writeFileSync(this.identityFile, JSON.stringify(identity, null, 2)); } async rotateKeys() { const keyPair = await this.cryptoProvider.generateKeyPair(); const did = this.generateDIDFromPublicKey(keyPair.publicKey); const identity = { did, kid: `${did}#key-1`, privateKey: keyPair.privateKey, publicKey: keyPair.publicKey, createdAt: new Date().toISOString(), type: 'development' }; await this.saveIdentity(identity); return identity; } async deleteIdentity() { try { fs.unlinkSync(this.identityFile); } catch { // Ignore if file doesn't exist } } generateDIDFromPublicKey(publicKey) { const keyHash = Buffer.from(publicKey, 'base64') .toString('base64url') .substring(0, 32); return `did:key:z${keyHash}`; } } exports.FileIdentityProvider = FileIdentityProvider; /** * Get nonce cache provider based on environment */ function getNonceCacheProvider() { const cacheType = process.env.MCPI_NONCE_CACHE_TYPE || process.env.XMCPI_NONCE_CACHE_TYPE || 'memory'; switch (cacheType) { case 'redis': // TODO: Add Redis support console.warn('Redis cache not yet supported in this version, falling back to memory'); return new mcp_i_core_1.MemoryNonceCacheProvider(); case 'dynamodb': // TODO: Add DynamoDB support console.warn('DynamoDB cache not yet supported in this version, falling back to memory'); return new mcp_i_core_1.MemoryNonceCacheProvider(); default: return new mcp_i_core_1.MemoryNonceCacheProvider(); } }