@aituber-onair/core
Version:
Core library for AITuber OnAir providing voice synthesis and chat processing
127 lines • 4.72 kB
JavaScript
export class ToolExecutor {
constructor() {
this.registry = new Map();
this.mcpServers = [];
}
register(definition, fn) {
if (this.registry.has(definition.name)) {
throw new Error(`Tool '${definition.name}' already registered`);
}
this.registry.set(definition.name, { def: definition, fn });
}
setMCPServers(servers) {
this.mcpServers = servers;
}
/**
* Check if a tool name is an MCP tool
* @param toolName Tool name to check
* @returns True if this is an MCP tool
*/
isMCPTool(toolName) {
return toolName.startsWith('mcp_') && toolName.includes('_');
}
/**
* Extract MCP server name from tool name
* @param toolName Tool name (e.g., "mcp_deepwiki_search")
* @returns Server name (e.g., "deepwiki")
*/
extractMCPServerName(toolName) {
// Format: mcp_{serverName}_{toolName}
const parts = toolName.split('_');
if (parts.length >= 3 && parts[0] === 'mcp') {
return parts[1]; // Return server name
}
throw new Error(`Invalid MCP tool name format: ${toolName}`);
}
/**
* Extract actual tool name from MCP tool name
* @param toolName Tool name (e.g., "mcp_deepwiki_search")
* @returns Actual tool name (e.g., "search")
*/
extractMCPToolName(toolName) {
// Format: mcp_{serverName}_{toolName}
const parts = toolName.split('_');
if (parts.length >= 3 && parts[0] === 'mcp') {
return parts.slice(2).join('_'); // Return everything after server name
}
throw new Error(`Invalid MCP tool name format: ${toolName}`);
}
/**
* Execute MCP tool call
* @param block Tool use block
* @returns Tool result block
*/
async executeMCPTool(block) {
const serverName = this.extractMCPServerName(block.name);
const toolName = this.extractMCPToolName(block.name);
const mcpServer = this.mcpServers.find((server) => server.name === serverName);
if (!mcpServer) {
throw new Error(`MCP server '${serverName}' not found`);
}
try {
// Prepare headers with optional authorization
const headers = {
'Content-Type': 'application/json',
};
if (mcpServer.authorization_token) {
headers['Authorization'] = `Bearer ${mcpServer.authorization_token}`;
}
// Make HTTP request to MCP server using proper MCP protocol
// Format: POST /tools/{toolName} with arguments in body
const response = await fetch(`${mcpServer.url}/tools/${toolName}`, {
method: 'POST',
headers,
body: JSON.stringify(block.input || {}),
});
if (!response.ok) {
throw new Error(`MCP server responded with ${response.status}: ${response.statusText}`);
}
const result = await response.json();
return {
type: 'tool_result',
tool_use_id: block.id,
content: typeof result === 'string' ? result : JSON.stringify(result),
};
}
catch (error) {
return {
type: 'tool_result',
tool_use_id: block.id,
content: `MCP tool execution failed: ${error.message}`,
};
}
}
async run(blocks) {
const tasks = blocks
.filter((b) => b.type === 'tool_use')
.map(async (b) => {
// Check if this is an MCP tool
if (this.isMCPTool(b.name)) {
return this.executeMCPTool(b);
}
// Handle regular tools
const entry = this.registry.get(b.name);
if (!entry) {
throw new Error(`Unhandled tool: ${b.name}`);
}
const { fn, def } = entry;
const resPromise = fn(b.input);
const result = def.config?.timeoutMs
? await Promise.race([
resPromise,
new Promise((_, rej) => setTimeout(() => rej(new Error(`${b.name} timed out`)), def.config.timeoutMs)),
])
: await resPromise;
return {
type: 'tool_result',
tool_use_id: b.id,
content: typeof result === 'string' ? result : JSON.stringify(result),
};
});
return Promise.all(tasks);
}
listDefinitions() {
return Array.from(this.registry.values()).map((r) => r.def);
}
}
//# sourceMappingURL=ToolExecutor.js.map