@kya-os/mcp-i
Version:
COMING SOON:Production-ready MCP Identity with automatic registration, key rotation, and optimized performance
728 lines (727 loc) • 30.9 kB
JavaScript
"use strict";
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MCPIdentity = exports.isAsyncRegistrationResponse = exports.pollRegistrationStatus = exports.showAgentStatus = exports.initWithDevExperience = exports.KeyRotationManager = exports.RuntimeDetector = exports.TransportFactory = exports.FileStorage = exports.MemoryStorage = exports.StorageFactory = exports.SilentLogger = exports.ConsoleLogger = exports.LoggerFactory = exports.resolveRegistries = exports.REGISTRY_TIERS = exports.RegistryFactory = void 0;
exports.enableMCPIdentity = enableMCPIdentity;
exports.createMCPMiddleware = createMCPMiddleware;
const crypto = __importStar(require("./crypto"));
const storage_1 = require("./storage");
const platform_info_1 = require("./platform-info");
const transport_1 = require("./transport");
const logger_1 = require("./logger");
const rotation_1 = require("./rotation");
const vercel_adapter_1 = require("./vercel-adapter");
const polling_1 = require("./polling");
__exportStar(require("./types"), exports);
__exportStar(require("./vercel-adapter"), exports);
var registry_1 = require("./registry");
Object.defineProperty(exports, "RegistryFactory", { enumerable: true, get: function () { return registry_1.RegistryFactory; } });
Object.defineProperty(exports, "REGISTRY_TIERS", { enumerable: true, get: function () { return registry_1.REGISTRY_TIERS; } });
Object.defineProperty(exports, "resolveRegistries", { enumerable: true, get: function () { return registry_1.resolveRegistries; } });
var logger_2 = require("./logger");
Object.defineProperty(exports, "LoggerFactory", { enumerable: true, get: function () { return logger_2.LoggerFactory; } });
Object.defineProperty(exports, "ConsoleLogger", { enumerable: true, get: function () { return logger_2.ConsoleLogger; } });
Object.defineProperty(exports, "SilentLogger", { enumerable: true, get: function () { return logger_2.SilentLogger; } });
var storage_2 = require("./storage");
Object.defineProperty(exports, "StorageFactory", { enumerable: true, get: function () { return storage_2.StorageFactory; } });
Object.defineProperty(exports, "MemoryStorage", { enumerable: true, get: function () { return storage_2.MemoryStorage; } });
Object.defineProperty(exports, "FileStorage", { enumerable: true, get: function () { return storage_2.FileStorage; } });
var transport_2 = require("./transport");
Object.defineProperty(exports, "TransportFactory", { enumerable: true, get: function () { return transport_2.TransportFactory; } });
Object.defineProperty(exports, "RuntimeDetector", { enumerable: true, get: function () { return transport_2.RuntimeDetector; } });
var rotation_2 = require("./rotation");
Object.defineProperty(exports, "KeyRotationManager", { enumerable: true, get: function () { return rotation_2.KeyRotationManager; } });
var dev_helper_1 = require("./dev-helper");
Object.defineProperty(exports, "initWithDevExperience", { enumerable: true, get: function () { return dev_helper_1.initWithDevExperience; } });
Object.defineProperty(exports, "showAgentStatus", { enumerable: true, get: function () { return dev_helper_1.showAgentStatus; } });
var polling_2 = require("./polling");
Object.defineProperty(exports, "pollRegistrationStatus", { enumerable: true, get: function () { return polling_2.pollRegistrationStatus; } });
Object.defineProperty(exports, "isAsyncRegistrationResponse", { enumerable: true, get: function () { return polling_2.isAsyncRegistrationResponse; } });
let globalIdentity = null;
class MCPIdentity {
constructor(identity, options = {}) {
this.usedNonces = new Set();
if (options.logger) {
logger_1.LoggerFactory.setLogger(options.logger);
}
else if (options.logLevel && options.logLevel !== 'silent') {
logger_1.LoggerFactory.setLogger(logger_1.LoggerFactory.createConsoleLogger(options.logLevel));
}
this.logger = (0, logger_1.getLogger)();
this.did = identity.did;
this.publicKey = identity.publicKey;
this.privateKey = identity.privateKey;
this.encryptionPassword = options.encryptionPassword;
this.timestampTolerance = options.timestampTolerance || 60000;
this.enableNonceTracking = options.enableNonceTracking !== false;
this.directories = identity.directories || 'verified';
this.storage = (typeof options?.storage === 'object' && 'load' in options.storage)
? options.storage
: storage_1.StorageFactory.create({
storage: options.storage,
customPath: options.persistencePath,
memoryKey: options.memoryKey || this.did,
encryptionPassword: options.encryptionPassword
});
this.transport = (typeof options?.transport === 'object' && 'post' in options.transport)
? options.transport
: transport_1.TransportFactory.create({
transport: options.transport
});
this.precomputed = {
did: this.did,
publicKey: this.publicKey,
didBytes: new TextEncoder().encode(this.did),
signatureCache: new Map()
};
if (options.storage !== 'memory') {
this.rotationManager = new rotation_1.KeyRotationManager(identity, this.transport, {});
}
if (this.enableNonceTracking) {
this.startNonceCleanup();
}
}
static async init(options) {
const logger = options?.logger || (0, logger_1.getLogger)(options?.logLevel);
if (globalIdentity) {
return globalIdentity;
}
const isVercel = process.env.VERCEL || process.env.VERCEL_ENV;
const isServerless = isVercel || process.env.AWS_LAMBDA_FUNCTION_NAME || process.env.FUNCTIONS_WORKER_RUNTIME;
options?.onProgress?.({
stage: 'checking_existing',
progress: 10,
message: 'Checking for existing identity...'
});
let identity = null;
if (isServerless || options?.storage === 'memory') {
identity = (0, vercel_adapter_1.loadIdentityFromEnv)();
if (identity) {
logger.info('✅ Loaded existing identity from environment variables');
options?.onProgress?.({
stage: 'complete',
progress: 100,
message: 'Loaded existing identity from environment',
data: { did: identity.did }
});
}
}
const storage = (typeof options?.storage === 'object' && 'load' in options.storage)
? options.storage
: storage_1.StorageFactory.create({
storage: options?.storage,
customPath: options?.persistencePath,
memoryKey: options?.memoryKey,
encryptionPassword: options?.encryptionPassword
});
if (!identity) {
identity = await storage.load();
}
if (identity) {
logger.info('Loaded existing identity:', identity.did);
options?.onProgress?.({
stage: 'complete',
progress: 100,
message: 'Loaded existing identity',
data: { did: identity.did }
});
globalIdentity = new MCPIdentity(identity, options);
return globalIdentity;
}
logger.info('No existing identity found, creating new identity...');
const transport = (typeof options?.transport === 'object' && 'post' in options.transport)
? options.transport
: transport_1.TransportFactory.create({
transport: options?.transport
});
const apiEndpoint = options?.apiEndpoint || 'https://knowthat.ai';
logger.info('Registering with knowthat.ai...');
logger.info('Generating cryptographic keys...');
options?.onProgress?.({
stage: 'generating_keys',
progress: 30,
message: 'Generating cryptographic keys...'
});
const keyPair = await crypto.generateKeyPair();
options?.onProgress?.({
stage: 'generating_keys',
progress: 50,
message: 'Keys generated successfully'
});
const registrationData = {
name: options?.name || process.env.MCP_SERVER_NAME || 'Unnamed MCP Server',
description: options?.description,
repository: options?.repository,
publicKey: keyPair.publicKey,
directories: options?.directories,
isDraft: options?.mode !== 'production'
};
logger.debug('Registration data:', {
name: registrationData.name,
hasDescription: !!registrationData.description,
hasRepository: !!registrationData.repository,
hasPublicKey: !!registrationData.publicKey,
directories: registrationData.directories,
isDraft: registrationData.isDraft
});
let response;
try {
options?.onProgress?.({
stage: 'registering',
progress: 60,
message: 'Registering with KYA-OS network...'
});
response = await autoRegister(transport, {
...registrationData,
apiEndpoint,
directories: options?.directories,
processingMode: options?.processingMode,
registryEndpoint: options?.registryEndpoint,
onProgress: options?.onProgress
});
options?.onProgress?.({
stage: 'registering',
progress: 80,
message: 'Registration successful'
});
}
catch (registrationError) {
logger.error('Failed to register with knowthat.ai:', registrationError.message);
if (options?.mode === 'development' || process.env.NODE_ENV === 'development') {
logger.warn('Running in offline development mode with temporary identity');
const tempSlug = `temp-${Date.now()}-${Math.random().toString(36).substring(7)}`;
response = {
did: `did:web:localhost:agents:${tempSlug}`,
agent: {
id: tempSlug,
slug: tempSlug,
name: registrationData.name,
url: `http://localhost:3000/agents/${tempSlug}`,
claimUrl: `http://localhost:3000/agents/claim?did=${tempSlug}`
},
keys: {
publicKey: keyPair.publicKey,
privateKey: keyPair.privateKey
}
};
logger.warn('⚠️ Using temporary development identity. This will not persist across restarts.');
logger.warn('⚠️ To use a permanent identity, ensure you can connect to knowthat.ai');
}
else {
throw registrationError;
}
}
identity = {
did: response.did,
publicKey: keyPair.publicKey,
privateKey: keyPair.privateKey,
agentId: response.agent.id,
agentSlug: response.agent.slug,
registeredAt: new Date().toISOString(),
directories: options?.directories || 'verified'
};
options?.onProgress?.({
stage: 'saving',
progress: 90,
message: 'Saving identity...'
});
await storage.save(identity);
options?.onProgress?.({
stage: 'complete',
progress: 100,
message: 'Identity created successfully',
data: {
did: identity.did,
publicKey: identity.publicKey,
agentId: identity.agentId,
agentSlug: identity.agentSlug,
claimUrl: response.agent.claimUrl
}
});
if (isServerless) {
(0, vercel_adapter_1.showVercelDeveloperInstructions)(identity, response.agent.claimUrl);
}
else {
logger.info('✅ Success! Your agent has been registered.');
logger.info(`DID: ${response.did}`);
logger.info(`Profile: ${response.agent.url}`);
if (response.agent.claimUrl) {
logger.info(`Claim your agent: ${response.agent.claimUrl}`);
}
if (options?.directories && options.directories !== 'none') {
const dirMessage = options.directories === 'verified'
? 'Your agent will be submitted to all verified directories'
: `Your agent will be submitted to: ${options.directories.join(', ')}`;
logger.info(dirMessage);
}
}
globalIdentity = new MCPIdentity(identity, options);
return globalIdentity;
}
async enableAutoRotation(policy) {
if (!this.rotationManager) {
throw new Error('Key rotation not available in memory storage mode');
}
this.rotationManager.setupAutoRotation((result) => {
if (result.success) {
this.logger.info('Keys rotated successfully');
this.persistIdentity();
}
else {
this.logger.error('Key rotation failed:', result.error);
}
});
}
async rotateKeys(reason) {
if (!this.rotationManager) {
throw new Error('Key rotation not available in memory storage mode');
}
const result = await this.rotationManager.rotateKeys(reason);
if (result.success) {
await this.persistIdentity();
}
return result;
}
checkKeyHealth() {
if (!this.rotationManager) {
return null;
}
return this.rotationManager.checkKeyHealth();
}
async getPrivateKey() {
if (this.decryptedPrivateKey) {
return this.decryptedPrivateKey;
}
if (this.encryptionPassword && this.privateKey.startsWith('enc:')) {
try {
this.decryptedPrivateKey = await crypto.decrypt(this.privateKey, this.encryptionPassword);
this.logger.debug('Private key decrypted successfully');
return this.decryptedPrivateKey;
}
catch (error) {
this.logger.error('Failed to decrypt private key:', error);
throw new Error('Invalid encryption password');
}
}
return this.privateKey;
}
async sign(message) {
const messageStr = typeof message === 'string' ? message : message.toString('base64');
const cached = this.precomputed.signatureCache.get(messageStr);
if (cached) {
return cached;
}
const privateKey = await this.getPrivateKey();
const signature = await crypto.sign(message, privateKey);
if (this.precomputed.signatureCache.size > 100) {
const firstKey = this.precomputed.signatureCache.keys().next().value;
if (firstKey) {
this.precomputed.signatureCache.delete(firstKey);
}
}
this.precomputed.signatureCache.set(messageStr, signature);
if (this.rotationManager) {
this.rotationManager.incrementSignatureCount();
}
return signature;
}
async requestEditAccess() {
const timestamp = Date.now();
const message = `edit-request:${this.did}:knowthat.ai:${timestamp}`;
const signature = await this.sign(message);
const baseUrl = 'https://knowthat.ai';
const editUrl = new URL(`${baseUrl}/agents/edit`);
editUrl.searchParams.set('did', this.did);
editUrl.searchParams.set('timestamp', timestamp.toString());
editUrl.searchParams.set('signature', signature);
const claimUrl = new URL(`${baseUrl}/agents/claim`);
claimUrl.searchParams.set('did', this.did);
claimUrl.searchParams.set('timestamp', timestamp.toString());
claimUrl.searchParams.set('signature', signature);
return {
editUrl: editUrl.toString(),
claimUrl: claimUrl.toString()
};
}
async verify(message, signature, publicKey) {
return crypto.verify(message, signature, publicKey || this.publicKey);
}
async respondToChallenge(challenge) {
const now = Date.now();
const challengeAge = now - challenge.timestamp;
if (challengeAge > this.timestampTolerance) {
throw new Error('Challenge expired');
}
if (challengeAge < 0) {
throw new Error('Challenge timestamp is in the future');
}
if (this.enableNonceTracking) {
if (this.usedNonces.has(challenge.nonce)) {
throw new Error('Nonce already used');
}
this.usedNonces.add(challenge.nonce);
}
const messageComponents = [
challenge.nonce,
challenge.timestamp.toString(),
this.did,
challenge.verifier_did || '',
(challenge.scope || []).join(',')
];
const message = messageComponents.join(':');
const signature = await this.sign(message);
return {
did: this.did,
signature,
timestamp: now,
nonce: challenge.nonce,
publicKey: this.publicKey
};
}
getCapabilities() {
return {
version: '1.0',
did: this.did,
publicKey: this.publicKey,
conformanceLevel: 2,
handshakeSupported: true,
handshakeEndpoint: '/_mcp-i/handshake',
verificationEndpoint: `https://knowthat.ai/api/agents/${this.did}/verify`,
registry: 'knowthat.ai'
};
}
async signResponse(response) {
const timestamp = new Date().toISOString();
const responseWithIdentity = {
...response,
_mcp_identity: {
did: this.did,
signature: '',
timestamp,
conformanceLevel: 2
}
};
const contentToSign = JSON.stringify({
...response,
_mcp_identity: {
did: this.did,
timestamp,
conformanceLevel: 2
}
});
responseWithIdentity._mcp_identity.signature = await this.sign(contentToSign);
return responseWithIdentity;
}
static generateNonce() {
return crypto.generateNonceSync();
}
getDirectories() {
return this.directories;
}
startNonceCleanup() {
this.nonceCleanupInterval = setInterval(() => {
if (this.usedNonces.size > 10000) {
this.usedNonces.clear();
}
}, this.timestampTolerance * 2);
}
destroy() {
if (this.nonceCleanupInterval) {
clearInterval(this.nonceCleanupInterval);
}
this.usedNonces.clear();
this.precomputed.signatureCache.clear();
}
extractAgentName() {
return process.env.MCP_SERVER_NAME || 'Unknown Agent';
}
extractAgentId() {
return process.env.AGENT_ID || '';
}
extractAgentSlug() {
const parts = this.did.split(':');
return parts[parts.length - 1];
}
async persistIdentity() {
try {
const identity = {
did: this.did,
publicKey: this.publicKey,
privateKey: this.privateKey,
agentId: this.extractAgentId(),
agentSlug: this.extractAgentSlug(),
registeredAt: new Date().toISOString(),
directories: this.directories
};
await this.storage.save(identity);
}
catch (error) {
this.logger.error('Failed to persist identity:', error);
}
}
}
exports.MCPIdentity = MCPIdentity;
async function enableMCPIdentity(options) {
const identity = await MCPIdentity.init(options);
try {
patchMCPServer(identity);
}
catch (error) {
const logger = (0, logger_1.getLogger)();
logger.debug('MCP Server not found, identity initialized for manual use');
}
return identity;
}
function createMCPMiddleware(identity) {
return (server) => {
if (!server || typeof server !== 'object') {
const logger = (0, logger_1.getLogger)();
logger.warn('Invalid MCP Server object passed to middleware');
return;
}
if (!server.setRequestHandler || typeof server.setRequestHandler !== 'function') {
const logger = (0, logger_1.getLogger)();
logger.warn('MCP Server missing setRequestHandler method');
return;
}
const originalSetRequestHandler = server.setRequestHandler.bind(server);
const originalConnect = server.connect ? server.connect.bind(server) : null;
server.setRequestHandler = function (method, handler) {
if (!method || typeof method !== 'string') {
const logger = (0, logger_1.getLogger)();
logger.warn('setRequestHandler called with invalid method:', method);
return originalSetRequestHandler.call(this, method, handler);
}
const wrappedHandler = async (...args) => {
const result = await handler(...args);
if (result && typeof result === 'object' && 'content' in result) {
return await identity.signResponse(result);
}
return result;
};
return originalSetRequestHandler(method, wrappedHandler);
};
if (originalConnect) {
server.connect = async function (transport) {
if (this.serverInfo && this.serverInfo.capabilities) {
this.serverInfo.capabilities['mcp-i'] = identity.getCapabilities();
}
this.setRequestHandler('mcp-i/challenge', async (request) => {
return identity.respondToChallenge(request.params);
});
return originalConnect.call(this, transport);
};
}
};
}
function patchMCPServer(identity) {
try {
const MCPModule = require('@modelcontextprotocol/sdk/server/index.js');
const OriginalServer = MCPModule.Server;
if (!OriginalServer) {
return;
}
const middleware = createMCPMiddleware(identity);
const OriginalConstructor = OriginalServer;
MCPModule.Server = function (...args) {
const instance = new OriginalConstructor(...args);
try {
middleware(instance, identity);
}
catch (error) {
const logger = (0, logger_1.getLogger)();
logger.warn('Failed to apply MCP-I middleware:', error);
}
return instance;
};
Object.setPrototypeOf(MCPModule.Server, OriginalConstructor);
Object.setPrototypeOf(MCPModule.Server.prototype, OriginalConstructor.prototype);
const logger = (0, logger_1.getLogger)();
logger.info('✨ MCP Server patched - all responses will be automatically signed');
}
catch (error) {
}
}
function determineEndpoint(options) {
if (options.registryEndpoint === 'cli')
return true;
if (options.registryEndpoint === 'auto-register')
return false;
if (options.registryEndpoint === 'auto' || !options.registryEndpoint) {
if (options.onProgress)
return true;
if (options.processingMode === 'sync')
return true;
if (process.stdout?.isTTY)
return true;
}
return false;
}
async function registerViaCLI(transport, options) {
const logger = (0, logger_1.getLogger)();
logger.debug('Using fast CLI registration endpoint');
const response = await transport.post(`${options.apiEndpoint}/api/agents/cli-register`, {
name: options.name,
description: options.description,
repository: options.repository,
publicKey: options.publicKey
}, {
timeout: 5000,
headers: {
'Content-Type': 'application/json',
'User-Agent': `-os/mcp-i/${(0, platform_info_1.generateClientInfo)().sdkVersion}`
}
});
logger.debug(`CLI registration completed in ${response.data.responseTime}ms`);
options.onProgress?.({
stage: 'registering',
progress: 80,
message: 'Registration successful',
data: {
did: response.data.did,
claimUrl: response.data.claimUrl
}
});
return {
success: response.data.success,
did: response.data.did,
agent: {
id: response.data.agent.id,
slug: response.data.agent.slug,
name: response.data.agent.name,
url: response.data.agent.url,
claimUrl: response.data.claimUrl
},
keys: response.data.keys || {
publicKey: options.publicKey || '',
privateKey: undefined
}
};
}
async function autoRegister(transport, options) {
const logger = (0, logger_1.getLogger)();
const shouldUseCLI = determineEndpoint(options);
if (shouldUseCLI) {
try {
return await registerViaCLI(transport, options);
}
catch (error) {
logger.debug(`CLI registration failed: ${error.message}, falling back to auto-register`);
}
}
try {
const requestBody = {
metadata: {
name: options.name,
description: options.description,
repository: options.repository,
publicKey: options.publicKey,
version: '1.0.0',
isDraft: options.isDraft,
directories: options.directories || 'verified'
},
clientInfo: (0, platform_info_1.generateClientInfo)({
processingMode: options.processingMode === 'auto' ? undefined : options.processingMode,
customMetadata: {
source: 'auto-register'
}
})
};
logger.debug('Sending registration request to:', `${options.apiEndpoint}/api/agents/auto-register`);
const response = await transport.post(`${options.apiEndpoint}/api/agents/auto-register`, requestBody, {
timeout: 30000,
headers: {
'Content-Type': 'application/json',
'User-Agent': `-os/mcp-i/${requestBody.clientInfo.sdkVersion}`
}
});
logger.debug('Registration response received:', {
status: response.status,
hasData: !!response.data,
isAsync: response.status === 202
});
if (response.status === 202 && (0, polling_1.isAsyncRegistrationResponse)(response.data)) {
logger.info('Registration submitted, waiting for completion...');
options.onProgress?.({
stage: 'registering',
progress: 70,
message: 'Registration submitted, processing...',
data: { jobId: response.data.jobId }
});
const result = await (0, polling_1.pollRegistrationStatus)(response.data.status, transport, {
pollInterval: 2000,
maxPollingTime: 60000,
logger,
onProgress: (message, progress) => {
options.onProgress?.({
stage: 'registering',
progress: 70 + (progress || 0) * 0.1,
message
});
}
});
logger.debug('Registration completed after polling:', {
hasDid: !!result.did,
hasAgent: !!result.agent
});
return result;
}
logger.debug('Got synchronous registration response');
return response.data;
}
catch (error) {
logger.error('Registration failed:', {
message: error.message,
status: error.status,
response: error.response
});
if (error.message?.includes('429')) {
throw new Error('Rate limit exceeded. Please try again later.');
}
if (error.message?.includes('500')) {
throw new Error('Server error during registration. This might be a temporary issue.\n' +
'Please try again in a few moments, or register manually at https://knowthat.ai/submit-agent');
}
if (error.message?.includes('400')) {
throw new Error('Invalid registration data. Please ensure all required fields are provided:\n' +
'- name: A unique name for your agent\n' +
'- publicKey: Generated automatically (check if generation succeeded)');
}
throw new Error(error.message || 'Failed to auto-register agent');
}
}