Files
sim/apps/sim/executor/handlers/function/function-handler.ts
Waleed 3d4b9f0665 feat(mcp): added support for mcp servers (#1296)
* update infra and remove railway

* feat(mcp): add mcp support

* consolidate mcp utils

* UI improvements, more MCP stuff

* cleanup placeholders

* reran migrations

* general improvements

* fix server side mcp exec

* more improvements, fixed search in environment settings tab

* persist subblock values for mcp block

* style fixes

* udpdate all text-primary to text-muted-foreground for visibility in dark mode

* Revert "update infra and remove railway"

This reverts commit dbf2b153b8f96808e7bb7e5f86f7e8624e3c12dd.

* make MCP servers workspace-scoped

* cleanup & remove unused dep

* consolidated utils, DRY

* added tests

* better error messages, confirmed that permissions works correctly

* additional improvements

* remove extraneous comments

* reran migrations

* lint

* style changes

* fix: prevent config mutation in MCP client URL retry logic

Fixed an issue where the MCP client was mutating the shared configuration
object's URL during retry attempts. This could cause configuration corruption
if the same config object was reused elsewhere.

* resolve PR comments

* ack PR comments
2025-09-09 17:18:08 -07:00

86 lines
2.8 KiB
TypeScript

import { DEFAULT_CODE_LANGUAGE } from '@/lib/execution/languages'
import { createLogger } from '@/lib/logs/console/logger'
import { BlockType } from '@/executor/consts'
import type { BlockHandler, ExecutionContext } from '@/executor/types'
import type { SerializedBlock } from '@/serializer/types'
import { executeTool } from '@/tools'
const logger = createLogger('FunctionBlockHandler')
/**
* Helper function to collect runtime block outputs and name mappings
* for tag resolution in function execution
*/
function collectBlockData(context: ExecutionContext): {
blockData: Record<string, any>
blockNameMapping: Record<string, string>
} {
const blockData: Record<string, any> = {}
const blockNameMapping: Record<string, string> = {}
for (const [id, state] of context.blockStates.entries()) {
if (state.output !== undefined) {
blockData[id] = state.output
const workflowBlock = context.workflow?.blocks?.find((b) => b.id === id)
if (workflowBlock?.metadata?.name) {
// Map both the display name and normalized form
blockNameMapping[workflowBlock.metadata.name] = id
const normalized = workflowBlock.metadata.name.replace(/\s+/g, '').toLowerCase()
blockNameMapping[normalized] = id
}
}
}
return { blockData, blockNameMapping }
}
/**
* Handler for Function blocks that execute custom code.
*/
export class FunctionBlockHandler implements BlockHandler {
canHandle(block: SerializedBlock): boolean {
return block.metadata?.id === BlockType.FUNCTION
}
async execute(
block: SerializedBlock,
inputs: Record<string, any>,
context: ExecutionContext
): Promise<any> {
const codeContent = Array.isArray(inputs.code)
? inputs.code.map((c: { content: string }) => c.content).join('\n')
: inputs.code
// Extract block data for variable resolution
const { blockData, blockNameMapping } = collectBlockData(context)
// Directly use the function_execute tool which calls the API route
const result = await executeTool(
'function_execute',
{
code: codeContent,
language: inputs.language || DEFAULT_CODE_LANGUAGE,
useLocalVM: !inputs.remoteExecution,
timeout: inputs.timeout || 5000,
envVars: context.environmentVariables || {},
workflowVariables: context.workflowVariables || {},
blockData: blockData, // Pass block data for variable resolution
blockNameMapping: blockNameMapping, // Pass block name to ID mapping
_context: {
workflowId: context.workflowId,
workspaceId: context.workspaceId,
},
},
false, // skipProxy
false, // skipPostProcess
context // execution context for file processing
)
if (!result.success) {
throw new Error(result.error || 'Function execution failed')
}
return result.output
}
}