@kya-os/mcp-i
Version:
The TypeScript MCP framework with identity features built-in
193 lines (192 loc) • 6.86 kB
JavaScript
;
/**
* Tool Protection Configuration and Resolution System (Phase 1.5)
*
* Enables zero-code tool protection by configuring which tools require delegation
* through configuration rather than manual code wrapping.
*
* Configuration Resolution Priority (highest to lowest):
* 1. Inline config (passed to runtime) - for testing and overrides
* 2. Local file (tool-protections.json) - for development
* 3. AgentShield API - for production (NOT IMPLEMENTED YET)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ToolProtectionResolver = exports.AgentShieldToolProtectionSource = exports.FileToolProtectionSource = exports.InlineToolProtectionSource = void 0;
exports.createToolProtectionResolver = createToolProtectionResolver;
/**
* Inline configuration source (highest priority)
*/
class InlineToolProtectionSource {
config;
name = 'inline';
priority = 100;
constructor(config) {
this.config = config;
}
async load() {
return this.config;
}
}
exports.InlineToolProtectionSource = InlineToolProtectionSource;
/**
* Local file configuration source
*/
class FileToolProtectionSource {
filePath;
name = 'file';
priority = 50;
constructor(filePath) {
this.filePath = filePath;
}
async load() {
try {
// Dynamic import to avoid bundling issues
const fs = await import('fs/promises');
const path = await import('path');
const fullPath = path.resolve(process.cwd(), this.filePath);
const content = await fs.readFile(fullPath, 'utf-8');
const config = JSON.parse(content);
// Validate structure
if (typeof config !== 'object' || config === null) {
console.warn(`[ToolProtection] Invalid config file at ${fullPath}: not an object`);
return null;
}
return config;
}
catch (error) {
// File not found is OK - just means no local config
if (error.code === 'ENOENT') {
return null;
}
console.warn(`[ToolProtection] Error loading config from ${this.filePath}:`, error);
return null;
}
}
}
exports.FileToolProtectionSource = FileToolProtectionSource;
/**
* AgentShield API configuration source (lowest priority)
* NOTE: This is a placeholder for future implementation
*/
class AgentShieldToolProtectionSource {
apiUrl;
agentDid;
apiKey;
name = 'agentshield';
priority = 10;
constructor(apiUrl, agentDid, apiKey) {
this.apiUrl = apiUrl;
this.agentDid = agentDid;
this.apiKey = apiKey;
}
async load() {
// TODO: Implement AgentShield API fetch
// GET /v1/agents/{agentDid}/tool-protections
// Headers: { Authorization: `Bearer ${apiKey}` }
console.warn('[ToolProtection] AgentShield API source not yet implemented');
return null;
}
}
exports.AgentShieldToolProtectionSource = AgentShieldToolProtectionSource;
/**
* Tool protection resolver - merges configs from multiple sources
*/
class ToolProtectionResolver {
sources = [];
mergedConfig = null;
debug;
constructor(options = {}) {
this.debug = options.debug || false;
}
/**
* Add a configuration source
*/
addSource(source) {
this.sources.push(source);
// Sort by priority (highest first)
this.sources.sort((a, b) => b.priority - a.priority);
}
/**
* Load and merge all configurations
*/
async resolve() {
if (this.mergedConfig) {
return this.mergedConfig;
}
const configs = [];
// Load from all sources in priority order
for (const source of this.sources) {
if (this.debug) {
console.error(`[ToolProtection] Loading config from source: ${source.name} (priority: ${source.priority})`);
}
const config = await source.load();
if (config) {
configs.push({ source: source.name, config });
if (this.debug) {
console.error(`[ToolProtection] Loaded ${Object.keys(config).length} tool configs from ${source.name}`);
}
}
}
// Merge configs (higher priority sources override lower priority)
// Start with lowest priority and overlay higher priority configs
this.mergedConfig = {};
for (let i = configs.length - 1; i >= 0; i--) {
const { source, config } = configs[i];
for (const [toolName, toolConfig] of Object.entries(config)) {
if (this.debug && this.mergedConfig[toolName]) {
console.error(`[ToolProtection] Tool "${toolName}" config from ${source} overrides previous config`);
}
this.mergedConfig[toolName] = toolConfig;
}
}
if (this.debug) {
console.error(`[ToolProtection] Final merged config has ${Object.keys(this.mergedConfig).length} protected tools`);
console.error('[ToolProtection] Protected tools:', Object.keys(this.mergedConfig));
}
return this.mergedConfig;
}
/**
* Get protection config for a specific tool
*/
getToolProtection(toolName) {
if (!this.mergedConfig) {
throw new Error('ToolProtectionResolver not initialized - call resolve() first');
}
return this.mergedConfig[toolName] || null;
}
/**
* Check if a tool requires delegation
*/
requiresDelegation(toolName) {
const config = this.getToolProtection(toolName);
return config?.requiresDelegation || false;
}
/**
* Get required scopes for a tool
*/
getRequiredScopes(toolName) {
const config = this.getToolProtection(toolName);
return config?.requiredScopes || [];
}
}
exports.ToolProtectionResolver = ToolProtectionResolver;
/**
* Create default tool protection resolver
*/
function createToolProtectionResolver(options) {
const resolver = new ToolProtectionResolver({ debug: options.debug });
// Add inline source if provided
if (options.inline) {
resolver.addSource(new InlineToolProtectionSource(options.inline));
}
// Add local file source unless explicitly disabled
if (options.localFile !== false) {
const filePath = options.localFile || 'tool-protections.json';
resolver.addSource(new FileToolProtectionSource(filePath));
}
// Add AgentShield source if configured
if (options.agentShield) {
resolver.addSource(new AgentShieldToolProtectionSource(options.agentShield.apiUrl, options.agentShield.agentDid, options.agentShield.apiKey));
}
return resolver;
}