anon-identity
Version:
Decentralized identity framework with DIDs, Verifiable Credentials, and privacy-preserving selective disclosure
624 lines • 22.9 kB
JavaScript
"use strict";
/**
* Function Registry for MCP
*
* Central registry for managing function definitions and execution across providers
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.FunctionRegistry = void 0;
const events_1 = require("events");
const types_1 = require("../types");
/**
* Function Registry
*/
class FunctionRegistry extends events_1.EventEmitter {
constructor(config = {
maxExecutionTime: 30000, // 30 seconds
maxMemoryUsage: 100 * 1024 * 1024, // 100MB
enableSandboxing: true,
allowDynamicRegistration: true,
securityLevel: 'moderate',
auditAllCalls: true,
cacheResults: true,
cacheTimeout: 300000 // 5 minutes
}) {
super();
this.config = config;
this.functions = new Map();
this.categories = new Map();
this.executionCache = new Map();
this.executionMetrics = new Map();
this.initializeBuiltinFunctions();
}
/**
* Register a function
*/
async registerFunction(definition, handler, options = {}) {
// Validate function definition
const validation = this.validateFunctionDefinition(definition);
if (!validation.valid) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.INVALID_FUNCTION_CALL,
message: `Invalid function definition: ${validation.errors.join(', ')}`,
timestamp: new Date(),
retryable: false
});
}
// Check if function already exists
if (this.functions.has(definition.name)) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.INVALID_FUNCTION_CALL,
message: `Function ${definition.name} already registered`,
timestamp: new Date(),
retryable: false
});
}
// Create function metadata
const metadata = {
id: `func-${definition.name}-${Date.now()}`,
name: definition.name,
version: '1.0.0',
author: 'system',
description: definition.description,
category: 'general',
tags: [],
deprecated: false,
createdAt: new Date(),
updatedAt: new Date(),
usageCount: 0,
averageExecutionTime: 0,
successRate: 1.0,
...options.metadata
};
// Create security configuration
const security = {
requiredScopes: [],
riskLevel: types_1.FunctionRiskLevel.LOW,
auditRequired: this.config.auditAllCalls,
approvalRequired: false,
...options.security
};
// Create registered function
const registeredFunction = {
definition,
handler,
metadata,
security,
enabled: options.enabled !== false
};
this.functions.set(definition.name, registeredFunction);
// Update category index
const category = metadata.category;
if (!this.categories.has(category)) {
this.categories.set(category, new Set());
}
this.categories.get(category).add(definition.name);
// Initialize metrics
this.executionMetrics.set(definition.name, {
calls: 0,
totalTime: 0,
errors: 0
});
this.emit('function_registered', {
name: definition.name,
metadata,
security
});
return metadata.id;
}
/**
* Execute a function
*/
async executeFunction(functionCall, context) {
const startTime = Date.now();
const functionName = functionCall.name;
try {
// Get registered function
const registeredFunction = this.functions.get(functionName);
if (!registeredFunction) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_NOT_FOUND,
message: `Function ${functionName} not found`,
timestamp: new Date(),
retryable: false
});
}
// Check if function is enabled
if (!registeredFunction.enabled) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_ERROR,
message: `Function ${functionName} is disabled`,
timestamp: new Date(),
retryable: false
});
}
// Validate arguments
const validation = this.validateFunctionArguments(registeredFunction.definition, functionCall.arguments);
if (!validation.valid) {
throw new types_1.MCPError({
code: types_1.MCPErrorCode.INVALID_FUNCTION_CALL,
message: `Invalid arguments: ${validation.errors.join(', ')}`,
timestamp: new Date(),
retryable: false
});
}
// Check cache if enabled
if (this.config.cacheResults) {
const cacheKey = this.generateCacheKey(functionCall);
const cached = this.executionCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp.getTime() < this.config.cacheTimeout) {
return {
functionCallId: functionCall.id || `exec-${Date.now()}`,
result: cached.result,
executionTime: 0,
timestamp: new Date(),
metadata: {
provider: context.provider,
model: context.model,
agentDID: context.agentDID,
sessionId: context.sessionId,
warnings: ['Result returned from cache']
}
};
}
}
// Execute function with timeout
const sanitizedArgs = validation.sanitizedArgs || functionCall.arguments;
const result = await this.executeWithTimeout(registeredFunction.handler, sanitizedArgs, context, this.config.maxExecutionTime);
const executionTime = Date.now() - startTime;
// Update metrics
this.updateExecutionMetrics(functionName, executionTime, true);
// Update function metadata
registeredFunction.metadata.usageCount++;
registeredFunction.metadata.updatedAt = new Date();
this.updateAverageExecutionTime(registeredFunction.metadata, executionTime);
this.updateSuccessRate(registeredFunction.metadata, true);
// Cache result if enabled
if (this.config.cacheResults) {
const cacheKey = this.generateCacheKey(functionCall);
this.executionCache.set(cacheKey, {
result,
timestamp: new Date()
});
}
const executionResult = {
functionCallId: functionCall.id || `exec-${Date.now()}`,
result,
executionTime,
timestamp: new Date(),
metadata: {
provider: context.provider,
model: context.model,
agentDID: context.agentDID,
sessionId: context.sessionId,
warnings: validation.warnings
}
};
this.emit('function_executed', {
functionName,
context,
result: executionResult
});
return executionResult;
}
catch (error) {
const executionTime = Date.now() - startTime;
// Update metrics
this.updateExecutionMetrics(functionName, executionTime, false);
// Update function metadata if function exists
const registeredFunction = this.functions.get(functionName);
if (registeredFunction) {
this.updateSuccessRate(registeredFunction.metadata, false);
}
const mcpError = error instanceof types_1.MCPError ? error : new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_ERROR,
message: error.message,
timestamp: new Date(),
retryable: false
});
this.emit('function_error', {
functionName,
context,
error: mcpError
});
return {
functionCallId: functionCall.id || `exec-${Date.now()}`,
result: null,
error: mcpError.message,
executionTime,
timestamp: new Date(),
metadata: {
provider: context.provider,
model: context.model,
agentDID: context.agentDID,
sessionId: context.sessionId
}
};
}
}
/**
* Get function definition by name
*/
getFunctionDefinition(name) {
const func = this.functions.get(name);
return func ? func.definition : null;
}
/**
* Get all function definitions
*/
getAllFunctionDefinitions() {
return Array.from(this.functions.values())
.filter(func => func.enabled)
.map(func => func.definition);
}
/**
* Get functions by category
*/
getFunctionsByCategory(category) {
const functionNames = this.categories.get(category) || new Set();
return Array.from(functionNames)
.map(name => this.functions.get(name))
.filter(func => func && func.enabled)
.map(func => func.definition);
}
/**
* Search functions
*/
searchFunctions(query) {
const lowerQuery = query.toLowerCase();
return Array.from(this.functions.values())
.filter(func => func.enabled && (func.definition.name.toLowerCase().includes(lowerQuery) ||
func.definition.description.toLowerCase().includes(lowerQuery) ||
func.metadata.tags.some(tag => tag.toLowerCase().includes(lowerQuery))))
.map(func => func.definition);
}
/**
* Enable/disable function
*/
setFunctionEnabled(name, enabled) {
const func = this.functions.get(name);
if (func) {
func.enabled = enabled;
this.emit('function_status_changed', { name, enabled });
}
}
/**
* Unregister function
*/
unregisterFunction(name) {
const func = this.functions.get(name);
if (func) {
this.functions.delete(name);
// Remove from category
const category = func.metadata.category;
this.categories.get(category)?.delete(name);
// Clear metrics
this.executionMetrics.delete(name);
// Clear cache entries
for (const [key] of this.executionCache) {
if (key.includes(name)) {
this.executionCache.delete(key);
}
}
this.emit('function_unregistered', { name });
return true;
}
return false;
}
/**
* Get function statistics
*/
getFunctionStatistics() {
const totalFunctions = this.functions.size;
const enabledFunctions = Array.from(this.functions.values())
.filter(func => func.enabled).length;
const functionsByCategory = {};
for (const [category, names] of this.categories) {
functionsByCategory[category] = names.size;
}
const metrics = Array.from(this.executionMetrics.entries())
.map(([name, stats]) => ({
name,
calls: stats.calls,
avgTime: stats.calls > 0 ? stats.totalTime / stats.calls : 0
}))
.sort((a, b) => b.calls - a.calls)
.slice(0, 10);
const totalCalls = Array.from(this.executionMetrics.values())
.reduce((sum, stats) => sum + stats.calls, 0);
const totalErrors = Array.from(this.executionMetrics.values())
.reduce((sum, stats) => sum + stats.errors, 0);
const errorRate = totalCalls > 0 ? totalErrors / totalCalls : 0;
return {
totalFunctions,
enabledFunctions,
functionsByCategory,
topFunctions: metrics,
errorRate
};
}
/**
* Validate function definition
*/
validateFunctionDefinition(definition) {
const errors = [];
const warnings = [];
// Check required fields
if (!definition.name || typeof definition.name !== 'string') {
errors.push('Function name is required and must be a string');
}
if (!definition.description || typeof definition.description !== 'string') {
errors.push('Function description is required and must be a string');
}
if (!definition.parameters || typeof definition.parameters !== 'object') {
errors.push('Function parameters are required');
}
// Validate name format
if (definition.name && !/^[a-zA-Z][a-zA-Z0-9_]*$/.test(definition.name)) {
errors.push('Function name must start with a letter and contain only letters, numbers, and underscores');
}
// Validate parameters
if (definition.parameters) {
if (definition.parameters.type !== 'object') {
errors.push('Parameters type must be "object"');
}
if (!definition.parameters.properties || typeof definition.parameters.properties !== 'object') {
errors.push('Parameters must have properties');
}
if (!Array.isArray(definition.parameters.required)) {
warnings.push('Parameters should specify required fields');
}
}
return {
valid: errors.length === 0,
errors,
warnings
};
}
/**
* Validate function arguments
*/
validateFunctionArguments(definition, args) {
const errors = [];
const warnings = [];
const sanitizedArgs = { ...args };
const { parameters } = definition;
// Check required parameters
if (parameters.required) {
for (const required of parameters.required) {
if (!(required in args)) {
errors.push(`Missing required parameter: ${required}`);
}
}
}
// Validate parameter types
for (const [name, value] of Object.entries(args)) {
const paramDef = parameters.properties[name];
if (!paramDef) {
warnings.push(`Unknown parameter: ${name}`);
continue;
}
const validation = this.validateParameterValue(value, paramDef);
if (!validation.valid) {
errors.push(`Invalid parameter ${name}: ${validation.error}`);
}
else if (validation.sanitized !== undefined) {
sanitizedArgs[name] = validation.sanitized;
}
}
return {
valid: errors.length === 0,
errors,
warnings,
sanitizedArgs: errors.length === 0 ? sanitizedArgs : undefined
};
}
/**
* Validate parameter value
*/
validateParameterValue(value, paramDef) {
// Type validation
switch (paramDef.type) {
case 'string':
if (typeof value !== 'string') {
return { valid: false, error: 'Expected string' };
}
break;
case 'number':
if (typeof value !== 'number' || isNaN(value)) {
return { valid: false, error: 'Expected number' };
}
break;
case 'boolean':
if (typeof value !== 'boolean') {
return { valid: false, error: 'Expected boolean' };
}
break;
case 'array':
if (!Array.isArray(value)) {
return { valid: false, error: 'Expected array' };
}
break;
case 'object':
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
return { valid: false, error: 'Expected object' };
}
break;
}
// Enum validation
if (paramDef.enum && !paramDef.enum.includes(value)) {
return { valid: false, error: `Value must be one of: ${paramDef.enum.join(', ')}` };
}
// Range validation for numbers
if (paramDef.type === 'number') {
if (paramDef.minimum !== undefined && value < paramDef.minimum) {
return { valid: false, error: `Value must be >= ${paramDef.minimum}` };
}
if (paramDef.maximum !== undefined && value > paramDef.maximum) {
return { valid: false, error: `Value must be <= ${paramDef.maximum}` };
}
}
// Pattern validation for strings
if (paramDef.type === 'string' && paramDef.pattern) {
const regex = new RegExp(paramDef.pattern);
if (!regex.test(value)) {
return { valid: false, error: `Value must match pattern: ${paramDef.pattern}` };
}
}
return { valid: true };
}
/**
* Execute function with timeout
*/
async executeWithTimeout(handler, args, context, timeout) {
return new Promise((resolve, reject) => {
const timer = setTimeout(() => {
reject(new types_1.MCPError({
code: types_1.MCPErrorCode.FUNCTION_TIMEOUT,
message: `Function execution timed out after ${timeout}ms`,
timestamp: new Date(),
retryable: false
}));
}, timeout);
handler(args, context)
.then(result => {
clearTimeout(timer);
resolve(result);
})
.catch(error => {
clearTimeout(timer);
reject(error);
});
});
}
/**
* Generate cache key for function call
*/
generateCacheKey(functionCall) {
const argsString = JSON.stringify(functionCall.arguments, Object.keys(functionCall.arguments).sort());
return `${functionCall.name}:${argsString}`;
}
/**
* Update execution metrics
*/
updateExecutionMetrics(name, executionTime, success) {
const metrics = this.executionMetrics.get(name);
if (metrics) {
metrics.calls++;
metrics.totalTime += executionTime;
if (!success) {
metrics.errors++;
}
}
}
/**
* Update average execution time
*/
updateAverageExecutionTime(metadata, executionTime) {
const totalTime = metadata.averageExecutionTime * (metadata.usageCount - 1) + executionTime;
metadata.averageExecutionTime = totalTime / metadata.usageCount;
}
/**
* Update success rate
*/
updateSuccessRate(metadata, success) {
const previousSuccesses = metadata.successRate * (metadata.usageCount - 1);
const newSuccesses = success ? previousSuccesses + 1 : previousSuccesses;
metadata.successRate = newSuccesses / metadata.usageCount;
}
/**
* Initialize built-in functions
*/
initializeBuiltinFunctions() {
// Register basic utility functions
this.registerFunction({
name: 'get_current_time',
description: 'Get the current date and time',
parameters: {
type: 'object',
properties: {
format: {
type: 'string',
description: 'Time format (iso, unix, readable)',
enum: ['iso', 'unix', 'readable']
}
},
required: []
}
}, async (args) => {
const now = new Date();
switch (args.format) {
case 'unix':
return Math.floor(now.getTime() / 1000);
case 'readable':
return now.toLocaleString();
default:
return now.toISOString();
}
}, {
metadata: {
category: 'utility',
tags: ['time', 'date', 'utility']
},
security: {
requiredScopes: [],
riskLevel: types_1.FunctionRiskLevel.LOW,
auditRequired: false,
approvalRequired: false
}
});
this.registerFunction({
name: 'generate_uuid',
description: 'Generate a random UUID',
parameters: {
type: 'object',
properties: {
version: {
type: 'number',
description: 'UUID version (4 only supported)',
enum: [4]
}
},
required: []
}
}, async (args) => {
// Simple UUID v4 generation
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}, {
metadata: {
category: 'utility',
tags: ['uuid', 'random', 'identifier']
},
security: {
requiredScopes: [],
riskLevel: types_1.FunctionRiskLevel.LOW,
auditRequired: false,
approvalRequired: false
}
});
}
/**
* Clear cache
*/
clearCache() {
this.executionCache.clear();
this.emit('cache_cleared');
}
/**
* Shutdown
*/
shutdown() {
this.executionCache.clear();
this.executionMetrics.clear();
this.functions.clear();
this.categories.clear();
this.removeAllListeners();
}
}
exports.FunctionRegistry = FunctionRegistry;
exports.default = FunctionRegistry;
//# sourceMappingURL=function-registry.js.map