@juspay/neurolink
Version:
Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio
357 lines • 12.1 kB
JavaScript
/**
* Agent and Workflow Exposure as MCP Tools
*
* Enables exposing NeuroLink agents and workflows as MCP tools,
* allowing external MCP clients to invoke complex AI operations
* through the standardized MCP protocol.
*
* @module mcp/agentExposure
* @since 8.39.0
*/
import { logger } from "../utils/logger.js";
import { withTimeout } from "../utils/async/withTimeout.js";
import { ErrorFactory } from "../utils/errorHandling.js";
/**
* Expose an agent as an MCP tool
*/
export function exposeAgentAsTool(agent, options = {}) {
const { prefix = "agent", defaultAnnotations = {}, includeMetadataInDescription = true, nameTransformer = (name) => name.toLowerCase().replace(/\s+/g, "_"), wrapWithContext = true, executionTimeout = 300000, // 5 minutes default
enableLogging = true, } = options;
// Generate tool name
const baseName = nameTransformer(agent.name);
const toolName = prefix ? `${prefix}_${baseName}` : baseName;
// Build description
let description = agent.description;
if (includeMetadataInDescription && agent.metadata) {
const metaParts = [];
if (agent.metadata.version) {
metaParts.push(`v${agent.metadata.version}`);
}
if (agent.metadata.category) {
metaParts.push(`category: ${agent.metadata.category}`);
}
if (agent.metadata.estimatedDuration) {
metaParts.push(`~${agent.metadata.estimatedDuration}ms`);
}
if (metaParts.length > 0) {
description += ` [${metaParts.join(", ")}]`;
}
}
// Build annotations
const annotations = {
...defaultAnnotations,
complexity: defaultAnnotations.complexity ?? "complex",
estimatedDuration: agent.metadata?.estimatedDuration ?? defaultAnnotations.estimatedDuration,
costHint: agent.metadata?.costHint ?? defaultAnnotations.costHint,
tags: [
...(defaultAnnotations.tags ?? []),
"agent",
...(agent.metadata?.tags ?? []),
],
};
// Build input schema with agent context
const inputSchema = agent.inputSchema ?? {
type: "object",
properties: {},
};
// Create execution wrapper
const execute = async (params, context) => {
const startTime = Date.now();
if (enableLogging) {
logger.debug(`[AgentExposure] Executing agent '${agent.id}' as tool '${toolName}'`);
}
try {
// Execute agent with context wrapper if enabled
const existingConfig = context?.config && typeof context.config === "object"
? context.config
: {};
const executionContext = wrapWithContext
? {
...context,
config: {
...existingConfig,
sourceType: "mcp-exposed-agent",
agentId: agent.id,
toolName,
},
}
: (context ?? {});
const result = await withTimeout(agent.execute(params, executionContext), executionTimeout);
const duration = Date.now() - startTime;
if (enableLogging) {
logger.debug(`[AgentExposure] Agent '${agent.id}' completed in ${duration}ms`);
}
return {
success: true,
data: result,
metadata: {
agentId: agent.id,
toolName,
executionTime: duration,
sourceType: "agent",
},
};
}
catch (error) {
const duration = Date.now() - startTime;
const agentError = error instanceof Error
? error
: ErrorFactory.toolExecutionFailed(toolName, new Error(String(error)));
if (enableLogging) {
logger.error(`[AgentExposure] Agent '${agent.id}' failed after ${duration}ms: ${agentError.message}`);
}
return {
success: false,
error: agentError.message,
metadata: {
agentId: agent.id,
toolName,
executionTime: duration,
sourceType: "agent",
},
};
}
};
const tool = {
name: toolName,
description,
inputSchema,
outputSchema: agent.outputSchema,
annotations,
execute,
metadata: {
sourceType: "agent",
sourceId: agent.id,
originalName: agent.name,
...agent.metadata,
},
};
return {
tool,
sourceType: "agent",
sourceId: agent.id,
toolName,
};
}
/**
* Expose a workflow as an MCP tool
*/
export function exposeWorkflowAsTool(workflow, options = {}) {
const { prefix = "workflow", defaultAnnotations = {}, includeMetadataInDescription = true, nameTransformer = (name) => name.toLowerCase().replace(/\s+/g, "_"), wrapWithContext = true, executionTimeout = 600000, // 10 minutes default for workflows
enableLogging = true, } = options;
// Generate tool name
const baseName = nameTransformer(workflow.name);
const toolName = prefix ? `${prefix}_${baseName}` : baseName;
// Build description
let description = workflow.description;
if (includeMetadataInDescription) {
const metaParts = [];
if (workflow.metadata?.version) {
metaParts.push(`v${workflow.metadata.version}`);
}
if (workflow.steps?.length) {
metaParts.push(`${workflow.steps.length} steps`);
}
if (workflow.metadata?.estimatedDuration) {
metaParts.push(`~${workflow.metadata.estimatedDuration}ms`);
}
if (metaParts.length > 0) {
description += ` [${metaParts.join(", ")}]`;
}
}
// Build annotations
const annotations = {
...defaultAnnotations,
complexity: defaultAnnotations.complexity ?? "complex",
estimatedDuration: workflow.metadata?.estimatedDuration ??
defaultAnnotations.estimatedDuration,
idempotentHint: workflow.metadata?.idempotent ?? defaultAnnotations.idempotentHint,
tags: [
...(defaultAnnotations.tags ?? []),
"workflow",
...(workflow.metadata?.tags ?? []),
],
};
// Build input schema
const inputSchema = workflow.inputSchema ?? {
type: "object",
properties: {},
};
// Create execution wrapper
const execute = async (params, context) => {
const startTime = Date.now();
if (enableLogging) {
logger.debug(`[WorkflowExposure] Executing workflow '${workflow.id}' as tool '${toolName}'`);
}
try {
// Execute workflow with context wrapper if enabled
const existingConfig = context?.config && typeof context.config === "object"
? context.config
: {};
const executionContext = wrapWithContext
? {
...context,
config: {
...existingConfig,
sourceType: "mcp-exposed-workflow",
workflowId: workflow.id,
toolName,
},
}
: (context ?? {});
const result = await withTimeout(workflow.execute(params, executionContext), executionTimeout);
const duration = Date.now() - startTime;
if (enableLogging) {
logger.debug(`[WorkflowExposure] Workflow '${workflow.id}' completed in ${duration}ms`);
}
return {
success: true,
data: result,
metadata: {
workflowId: workflow.id,
toolName,
executionTime: duration,
sourceType: "workflow",
stepsCount: workflow.steps?.length,
},
};
}
catch (error) {
const duration = Date.now() - startTime;
const workflowError = error instanceof Error
? error
: ErrorFactory.toolExecutionFailed(toolName, new Error(String(error)));
if (enableLogging) {
logger.error(`[WorkflowExposure] Workflow '${workflow.id}' failed after ${duration}ms: ${workflowError.message}`);
}
return {
success: false,
error: workflowError.message,
metadata: {
workflowId: workflow.id,
toolName,
executionTime: duration,
sourceType: "workflow",
},
};
}
};
const tool = {
name: toolName,
description,
inputSchema,
outputSchema: workflow.outputSchema,
annotations,
execute,
metadata: {
sourceType: "workflow",
sourceId: workflow.id,
originalName: workflow.name,
steps: workflow.steps,
...workflow.metadata,
},
};
return {
tool,
sourceType: "workflow",
sourceId: workflow.id,
toolName,
};
}
/**
* Batch expose agents as MCP tools
*/
export function exposeAgentsAsTools(agents, options = {}) {
return agents.map((agent) => exposeAgentAsTool(agent, options));
}
/**
* Batch expose workflows as MCP tools
*/
export function exposeWorkflowsAsTools(workflows, options = {}) {
return workflows.map((workflow) => exposeWorkflowAsTool(workflow, options));
}
/**
* Agent Exposure Manager
*
* Manages the lifecycle of exposed agents and workflows,
* providing registration, lookup, and invocation capabilities.
*/
export class AgentExposureManager {
exposedTools = new Map();
options;
constructor(options = {}) {
this.options = options;
}
/**
* Expose an agent and register it
*/
exposeAgent(agent) {
const result = exposeAgentAsTool(agent, this.options);
this.exposedTools.set(result.toolName, result);
return result.tool;
}
/**
* Expose a workflow and register it
*/
exposeWorkflow(workflow) {
const result = exposeWorkflowAsTool(workflow, this.options);
this.exposedTools.set(result.toolName, result);
return result.tool;
}
/**
* Get all exposed tools
*/
getExposedTools() {
return Array.from(this.exposedTools.values()).map((r) => r.tool);
}
/**
* Get exposed tool by name
*/
getExposedTool(toolName) {
return this.exposedTools.get(toolName)?.tool;
}
/**
* Get exposure result by tool name
*/
getExposureResult(toolName) {
return this.exposedTools.get(toolName);
}
/**
* Get tools by source type
*/
getToolsBySourceType(sourceType) {
return Array.from(this.exposedTools.values())
.filter((r) => r.sourceType === sourceType)
.map((r) => r.tool);
}
/**
* Remove exposed tool
*/
unexpose(toolName) {
return this.exposedTools.delete(toolName);
}
/**
* Clear all exposed tools
*/
clear() {
this.exposedTools.clear();
}
/**
* Get statistics
*/
getStatistics() {
const results = Array.from(this.exposedTools.values());
return {
totalExposed: results.length,
exposedAgents: results.filter((r) => r.sourceType === "agent").length,
exposedWorkflows: results.filter((r) => r.sourceType === "workflow")
.length,
toolNames: results.map((r) => r.toolName),
};
}
}
/**
* Global agent exposure manager instance
*/
export const globalAgentExposureManager = new AgentExposureManager();
//# sourceMappingURL=agentExposure.js.map