@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
369 lines (368 loc) • 13.3 kB
JavaScript
;
/**
* 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();
}
}