mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-31 17:58:04 -05:00
mcp v1
This commit is contained in:
@@ -21,6 +21,152 @@ const logger = createLogger('CopilotMcpAPI')
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
/**
|
||||
* MCP Server instructions that guide LLMs on how to use the Sim copilot tools.
|
||||
* This is included in the initialize response to help external LLMs understand
|
||||
* the workflow lifecycle and best practices.
|
||||
*/
|
||||
const MCP_SERVER_INSTRUCTIONS = `
|
||||
## Sim Workflow Copilot - Usage Guide
|
||||
|
||||
You are interacting with Sim's workflow automation platform. These tools orchestrate specialized AI agents that build workflows. Follow these guidelines carefully.
|
||||
|
||||
---
|
||||
|
||||
## Platform Knowledge
|
||||
|
||||
Sim is a workflow automation platform. Workflows are visual pipelines of blocks.
|
||||
|
||||
### Block Types
|
||||
|
||||
**Core Logic:**
|
||||
- **Agent** - The heart of Sim (LLM block with tools, memory, structured output, knowledge bases)
|
||||
- **Function** - JavaScript code execution
|
||||
- **Condition** - If/else branching
|
||||
- **Router** - AI-powered content-based routing
|
||||
- **Loop** - While/do-while iteration
|
||||
- **Parallel** - Simultaneous execution
|
||||
- **API** - HTTP requests
|
||||
|
||||
**Integrations (3rd Party):**
|
||||
- OAuth: Slack, Gmail, Google Calendar, Sheets, Outlook, Linear, GitHub, Notion
|
||||
- API: Stripe, Twilio, SendGrid, any REST API
|
||||
|
||||
### The Agent Block
|
||||
|
||||
The Agent block is the core of intelligent workflows:
|
||||
- **Tools** - Add integrations, custom tools, web search to give it capabilities
|
||||
- **Memory** - Multi-turn conversations with persistent context
|
||||
- **Structured Output** - JSON schema for reliable parsing
|
||||
- **Knowledge Bases** - RAG-powered document retrieval
|
||||
|
||||
**Design principle:** Put tools INSIDE agents rather than using standalone tool blocks.
|
||||
|
||||
### Triggers
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| Manual/Chat | User sends message in UI (start block: input, files, conversationId) |
|
||||
| API | REST endpoint with custom input schema |
|
||||
| Webhook | External services POST to trigger URL |
|
||||
| Schedule | Cron-based (hourly, daily, weekly) |
|
||||
|
||||
### Deployments
|
||||
|
||||
| Type | Trigger | Use Case |
|
||||
|------|---------|----------|
|
||||
| API | Start block | REST endpoint for programmatic access |
|
||||
| Chat | Start block | Managed chat UI with auth options |
|
||||
| MCP | Start block | Expose as MCP tool for AI agents |
|
||||
| General | Schedule/Webhook | Activate triggers to run automatically |
|
||||
|
||||
**Undeployed workflows only run in the builder UI.**
|
||||
|
||||
### Variable Syntax
|
||||
|
||||
Reference outputs from previous blocks: \`<blockname.field>\`
|
||||
Reference environment variables: \`{{ENV_VAR_NAME}}\`
|
||||
|
||||
Rules:
|
||||
- Block names must be lowercase, no spaces, no special characters
|
||||
- Use dot notation for nested fields: \`<blockname.field.subfield>\`
|
||||
|
||||
---
|
||||
|
||||
## Workflow Lifecycle
|
||||
|
||||
1. **Create**: For NEW workflows, FIRST call create_workflow to get a workflowId
|
||||
2. **Plan**: Use copilot_plan with the workflowId to plan the workflow
|
||||
3. **Edit**: Use copilot_edit with the workflowId AND the plan to build the workflow
|
||||
4. **Deploy**: ALWAYS deploy after building using copilot_deploy before testing/running
|
||||
5. **Test**: Use copilot_test to verify the workflow works correctly
|
||||
6. **Share**: Provide the user with the workflow URL after completion
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Always Pass workflowId
|
||||
|
||||
- For NEW workflows: Call create_workflow FIRST, then use the returned workflowId
|
||||
- For EXISTING workflows: Pass the workflowId to all copilot tools
|
||||
- copilot_plan, copilot_edit, copilot_deploy, copilot_test, copilot_debug all REQUIRE workflowId
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: How to Handle Plans
|
||||
|
||||
The copilot_plan tool returns a structured plan object. You MUST:
|
||||
|
||||
1. **Do NOT modify the plan**: Pass the plan object EXACTLY as returned to copilot_edit
|
||||
2. **Do NOT interpret or summarize the plan**: The edit agent needs the raw plan data
|
||||
3. **Pass the plan in the context.plan field**: \`{ "context": { "plan": <plan_object> } }\`
|
||||
4. **Include ALL plan data**: Block configurations, connections, credentials, everything
|
||||
|
||||
Example flow:
|
||||
\`\`\`
|
||||
1. copilot_plan({ request: "build a workflow...", workflowId: "abc123" })
|
||||
-> Returns: { "plan": { "blocks": [...], "connections": [...], ... } }
|
||||
|
||||
2. copilot_edit({
|
||||
workflowId: "abc123",
|
||||
message: "Execute the plan",
|
||||
context: { "plan": <EXACT plan object from step 1> }
|
||||
})
|
||||
\`\`\`
|
||||
|
||||
**Why this matters**: The plan contains technical details (block IDs, field mappings, API schemas) that the edit agent needs verbatim. Summarizing or rephrasing loses critical information.
|
||||
|
||||
---
|
||||
|
||||
## CRITICAL: Error Handling
|
||||
|
||||
**If the user says "doesn't work", "broke", "failed", "error" → ALWAYS use copilot_debug FIRST.**
|
||||
|
||||
Don't guess. Don't plan. Debug first to find the actual problem.
|
||||
|
||||
---
|
||||
|
||||
## Important Rules
|
||||
|
||||
- ALWAYS deploy a workflow before attempting to run or test it
|
||||
- Workflows must be deployed to have an "active deployment" for execution
|
||||
- After building, call copilot_deploy with the appropriate deployment type (api, chat, or mcp)
|
||||
- Return the workflow URL to the user so they can access it in Sim
|
||||
|
||||
---
|
||||
|
||||
## Quick Operations (use direct tools)
|
||||
- list_workflows, list_workspaces, list_folders, get_workflow: Fast database queries
|
||||
- create_workflow: Create new workflow and get workflowId (CALL THIS FIRST for new workflows)
|
||||
- create_folder: Create new resources
|
||||
|
||||
## Workflow Building (use copilot tools)
|
||||
- copilot_plan: Plan workflow changes (REQUIRES workflowId) - returns a plan object
|
||||
- copilot_edit: Execute the plan (REQUIRES workflowId AND plan from copilot_plan)
|
||||
- copilot_deploy: Deploy workflows (REQUIRES workflowId)
|
||||
- copilot_test: Test workflow execution (REQUIRES workflowId)
|
||||
- copilot_debug: Diagnose errors (REQUIRES workflowId) - USE THIS FIRST for issues
|
||||
`
|
||||
|
||||
/**
|
||||
* Direct tools that execute immediately without LLM orchestration.
|
||||
* These are fast database queries that don't need AI reasoning.
|
||||
@@ -91,6 +237,56 @@ const DIRECT_TOOL_DEFS: Array<{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_workflow',
|
||||
toolId: 'create_workflow',
|
||||
description: 'Create a new workflow. Returns the new workflow ID.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name for the new workflow.',
|
||||
},
|
||||
workspaceId: {
|
||||
type: 'string',
|
||||
description: 'Optional workspace ID. Uses default workspace if not provided.',
|
||||
},
|
||||
folderId: {
|
||||
type: 'string',
|
||||
description: 'Optional folder ID to place the workflow in.',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Optional description for the workflow.',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'create_folder',
|
||||
toolId: 'create_folder',
|
||||
description: 'Create a new folder in a workspace.',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Name for the new folder.',
|
||||
},
|
||||
workspaceId: {
|
||||
type: 'string',
|
||||
description: 'Optional workspace ID. Uses default workspace if not provided.',
|
||||
},
|
||||
parentId: {
|
||||
type: 'string',
|
||||
description: 'Optional parent folder ID for nested folders.',
|
||||
},
|
||||
},
|
||||
required: ['name'],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const SUBAGENT_TOOL_DEFS: Array<{
|
||||
@@ -127,28 +323,71 @@ DO NOT USE (use direct tools instead):
|
||||
{
|
||||
name: 'copilot_plan',
|
||||
agentId: 'plan',
|
||||
description: 'Plan workflow changes by gathering required information.',
|
||||
description: `Plan workflow changes by gathering required information.
|
||||
|
||||
USE THIS WHEN:
|
||||
- Building a new workflow
|
||||
- Modifying an existing workflow
|
||||
- You need to understand what blocks and integrations are available
|
||||
- The workflow requires multiple blocks or connections
|
||||
|
||||
WORKFLOW ID (REQUIRED):
|
||||
- For NEW workflows: First call create_workflow to get a workflowId, then pass it here
|
||||
- For EXISTING workflows: Always pass the workflowId parameter
|
||||
|
||||
This tool gathers information about available blocks, credentials, and the current workflow state.
|
||||
|
||||
RETURNS: A plan object containing block configurations, connections, and technical details.
|
||||
IMPORTANT: Pass the returned plan EXACTLY to copilot_edit - do not modify or summarize it.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
request: { type: 'string' },
|
||||
workflowId: { type: 'string' },
|
||||
request: { type: 'string', description: 'What you want to build or modify in the workflow.' },
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
description: 'REQUIRED. The workflow ID. For new workflows, call create_workflow first to get this.',
|
||||
},
|
||||
context: { type: 'object' },
|
||||
},
|
||||
required: ['request'],
|
||||
required: ['request', 'workflowId'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'copilot_edit',
|
||||
agentId: 'edit',
|
||||
description: 'Execute a workflow plan and apply edits.',
|
||||
description: `Execute a workflow plan and apply edits.
|
||||
|
||||
USE THIS WHEN:
|
||||
- You have a plan from copilot_plan that needs to be executed
|
||||
- Building or modifying a workflow based on the plan
|
||||
- Making changes to blocks, connections, or configurations
|
||||
|
||||
WORKFLOW ID (REQUIRED):
|
||||
- You MUST provide the workflowId parameter
|
||||
- For new workflows, get the workflowId from create_workflow first
|
||||
|
||||
PLAN (REQUIRED):
|
||||
- Pass the EXACT plan object from copilot_plan in the context.plan field
|
||||
- Do NOT modify, summarize, or interpret the plan - pass it verbatim
|
||||
- The plan contains technical details the edit agent needs exactly as-is
|
||||
|
||||
IMPORTANT: After copilot_edit completes, you MUST call copilot_deploy before the workflow can be run or tested.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
message: { type: 'string' },
|
||||
workflowId: { type: 'string' },
|
||||
plan: { type: 'object' },
|
||||
context: { type: 'object' },
|
||||
message: { type: 'string', description: 'Optional additional instructions for the edit.' },
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
description: 'REQUIRED. The workflow ID to edit. Get this from create_workflow for new workflows.',
|
||||
},
|
||||
plan: {
|
||||
type: 'object',
|
||||
description: 'The plan object from copilot_plan. Pass it EXACTLY as returned, do not modify.',
|
||||
},
|
||||
context: {
|
||||
type: 'object',
|
||||
description: 'Additional context. Put the plan in context.plan if not using the plan field directly.',
|
||||
},
|
||||
},
|
||||
required: ['workflowId'],
|
||||
},
|
||||
@@ -156,29 +395,54 @@ DO NOT USE (use direct tools instead):
|
||||
{
|
||||
name: 'copilot_debug',
|
||||
agentId: 'debug',
|
||||
description: 'Diagnose errors or unexpected workflow behavior.',
|
||||
description: `Diagnose errors or unexpected workflow behavior.
|
||||
|
||||
WORKFLOW ID (REQUIRED): Always provide the workflowId of the workflow to debug.`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
error: { type: 'string' },
|
||||
workflowId: { type: 'string' },
|
||||
error: { type: 'string', description: 'The error message or description of the issue.' },
|
||||
workflowId: { type: 'string', description: 'REQUIRED. The workflow ID to debug.' },
|
||||
context: { type: 'object' },
|
||||
},
|
||||
required: ['error'],
|
||||
required: ['error', 'workflowId'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'copilot_deploy',
|
||||
agentId: 'deploy',
|
||||
description: 'Deploy or manage workflow deployments.',
|
||||
description: `Deploy or manage workflow deployments.
|
||||
|
||||
CRITICAL: You MUST deploy a workflow after building before it can be run or tested.
|
||||
Workflows without an active deployment will fail with "no active deployment" error.
|
||||
|
||||
WORKFLOW ID (REQUIRED):
|
||||
- Always provide the workflowId parameter
|
||||
- This must match the workflow you built with copilot_edit
|
||||
|
||||
USE THIS:
|
||||
- After copilot_edit completes to activate the workflow
|
||||
- To update deployment settings
|
||||
- To redeploy after making changes
|
||||
|
||||
DEPLOYMENT TYPES:
|
||||
- "deploy as api" - REST API endpoint
|
||||
- "deploy as chat" - Chat interface
|
||||
- "deploy as mcp" - MCP server`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
request: { type: 'string' },
|
||||
workflowId: { type: 'string' },
|
||||
request: {
|
||||
type: 'string',
|
||||
description: 'The deployment request, e.g. "deploy as api" or "deploy as chat"',
|
||||
},
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
description: 'REQUIRED. The workflow ID to deploy.',
|
||||
},
|
||||
context: { type: 'object' },
|
||||
},
|
||||
required: ['request'],
|
||||
required: ['request', 'workflowId'],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -277,15 +541,29 @@ DO NOT USE (use direct tools instead):
|
||||
{
|
||||
name: 'copilot_test',
|
||||
agentId: 'test',
|
||||
description: 'Run workflows and verify outputs.',
|
||||
description: `Run workflows and verify outputs.
|
||||
|
||||
PREREQUISITE: The workflow MUST be deployed first using copilot_deploy.
|
||||
Undeployed workflows will fail with "no active deployment" error.
|
||||
|
||||
WORKFLOW ID (REQUIRED):
|
||||
- Always provide the workflowId parameter
|
||||
|
||||
USE THIS:
|
||||
- After deploying to verify the workflow works correctly
|
||||
- To test with sample inputs
|
||||
- To validate workflow behavior before sharing with user`,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
request: { type: 'string' },
|
||||
workflowId: { type: 'string' },
|
||||
workflowId: {
|
||||
type: 'string',
|
||||
description: 'REQUIRED. The workflow ID to test.',
|
||||
},
|
||||
context: { type: 'object' },
|
||||
},
|
||||
required: ['request'],
|
||||
required: ['request', 'workflowId'],
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -355,7 +633,8 @@ export async function POST(request: NextRequest) {
|
||||
const result: InitializeResult = {
|
||||
protocolVersion: '2024-11-05',
|
||||
capabilities: { tools: {} },
|
||||
serverInfo: { name: 'copilot-subagents', version: '1.0.0' },
|
||||
serverInfo: { name: 'sim-copilot', version: '1.0.0' },
|
||||
instructions: MCP_SERVER_INSTRUCTIONS,
|
||||
}
|
||||
return NextResponse.json(createResponse(id, result))
|
||||
}
|
||||
@@ -391,9 +670,9 @@ async function handleToolsList(id: RequestId): Promise<NextResponse> {
|
||||
}))
|
||||
|
||||
const subagentTools = SUBAGENT_TOOL_DEFS.map((tool) => ({
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: tool.inputSchema,
|
||||
}))
|
||||
|
||||
const result: ListToolsResult = {
|
||||
@@ -416,17 +695,17 @@ async function handleToolsCall(
|
||||
return handleDirectToolCall(id, directTool, args, userId)
|
||||
}
|
||||
|
||||
// Check if this is a subagent tool (slower, uses LLM)
|
||||
// Check if this is a subagent tool (uses LLM orchestration)
|
||||
const subagentTool = SUBAGENT_TOOL_DEFS.find((tool) => tool.name === params.name)
|
||||
if (subagentTool) {
|
||||
return handleSubagentToolCall(id, subagentTool, args, userId)
|
||||
}
|
||||
|
||||
return NextResponse.json(
|
||||
createError(id, ErrorCode.MethodNotFound, `Tool not found: ${params.name}`),
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
return NextResponse.json(
|
||||
createError(id, ErrorCode.MethodNotFound, `Tool not found: ${params.name}`),
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
async function handleDirectToolCall(
|
||||
id: RequestId,
|
||||
@@ -494,6 +773,9 @@ async function handleSubagentToolCall(
|
||||
workspaceId: args.workspaceId,
|
||||
context,
|
||||
model,
|
||||
// Signal to the copilot backend that this is a headless request
|
||||
// so it can enforce workflowId requirements on tools
|
||||
headless: true,
|
||||
},
|
||||
{
|
||||
userId,
|
||||
|
||||
@@ -65,6 +65,20 @@ async function executeToolAndReport(
|
||||
toolCall.error = result.error
|
||||
toolCall.endTime = Date.now()
|
||||
|
||||
// If create_workflow was successful, update the execution context with the new workflowId
|
||||
// This ensures subsequent tools in the same stream have access to the workflowId
|
||||
if (
|
||||
toolCall.name === 'create_workflow' &&
|
||||
result.success &&
|
||||
result.output?.workflowId &&
|
||||
!execContext.workflowId
|
||||
) {
|
||||
execContext.workflowId = result.output.workflowId
|
||||
if (result.output.workspaceId) {
|
||||
execContext.workspaceId = result.output.workspaceId
|
||||
}
|
||||
}
|
||||
|
||||
await markToolComplete(
|
||||
toolCall.id,
|
||||
toolCall.name,
|
||||
|
||||
@@ -121,7 +121,14 @@ async function executeServerToolDirect(
|
||||
context: ExecutionContext
|
||||
): Promise<ToolCallResult> {
|
||||
try {
|
||||
const result = await routeExecution(toolName, params, { userId: context.userId })
|
||||
// Inject workflowId from context if not provided in params
|
||||
// This is needed for tools like set_environment_variables that require workflowId
|
||||
const enrichedParams = { ...params }
|
||||
if (!enrichedParams.workflowId && context.workflowId) {
|
||||
enrichedParams.workflowId = context.workflowId
|
||||
}
|
||||
|
||||
const result = await routeExecution(toolName, enrichedParams, { userId: context.userId })
|
||||
return { success: true, output: result }
|
||||
} catch (error) {
|
||||
logger.error('Server tool execution failed', {
|
||||
|
||||
@@ -1401,6 +1401,101 @@ function filterDisallowedTools(
|
||||
return allowedTools
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes block IDs in operations to ensure they are valid UUIDs.
|
||||
* The LLM may generate human-readable IDs like "web_search" or "research_agent"
|
||||
* which need to be converted to proper UUIDs for database compatibility.
|
||||
*
|
||||
* Returns the normalized operations and a mapping from old IDs to new UUIDs.
|
||||
*/
|
||||
function normalizeBlockIdsInOperations(operations: EditWorkflowOperation[]): {
|
||||
normalizedOperations: EditWorkflowOperation[]
|
||||
idMapping: Map<string, string>
|
||||
} {
|
||||
const logger = createLogger('EditWorkflowServerTool')
|
||||
const idMapping = new Map<string, string>()
|
||||
|
||||
// First pass: collect all non-UUID block_ids from add/insert operations
|
||||
for (const op of operations) {
|
||||
if (op.operation_type === 'add' || op.operation_type === 'insert_into_subflow') {
|
||||
if (op.block_id && !UUID_REGEX.test(op.block_id)) {
|
||||
const newId = crypto.randomUUID()
|
||||
idMapping.set(op.block_id, newId)
|
||||
logger.debug('Normalizing block ID', { oldId: op.block_id, newId })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (idMapping.size === 0) {
|
||||
return { normalizedOperations: operations, idMapping }
|
||||
}
|
||||
|
||||
logger.info('Normalizing block IDs in operations', {
|
||||
normalizedCount: idMapping.size,
|
||||
mappings: Object.fromEntries(idMapping),
|
||||
})
|
||||
|
||||
// Helper to replace an ID if it's in the mapping
|
||||
const replaceId = (id: string | undefined): string | undefined => {
|
||||
if (!id) return id
|
||||
return idMapping.get(id) ?? id
|
||||
}
|
||||
|
||||
// Second pass: update all references to use new UUIDs
|
||||
const normalizedOperations = operations.map((op) => {
|
||||
const normalized: EditWorkflowOperation = {
|
||||
...op,
|
||||
block_id: replaceId(op.block_id) ?? op.block_id,
|
||||
}
|
||||
|
||||
if (op.params) {
|
||||
normalized.params = { ...op.params }
|
||||
|
||||
// Update subflowId references (for insert_into_subflow)
|
||||
if (normalized.params.subflowId) {
|
||||
normalized.params.subflowId = replaceId(normalized.params.subflowId)
|
||||
}
|
||||
|
||||
// Update connection references
|
||||
if (normalized.params.connections) {
|
||||
const normalizedConnections: Record<string, any> = {}
|
||||
for (const [handle, targets] of Object.entries(normalized.params.connections)) {
|
||||
if (typeof targets === 'string') {
|
||||
normalizedConnections[handle] = replaceId(targets)
|
||||
} else if (Array.isArray(targets)) {
|
||||
normalizedConnections[handle] = targets.map((t) => {
|
||||
if (typeof t === 'string') return replaceId(t)
|
||||
if (t && typeof t === 'object' && t.block) {
|
||||
return { ...t, block: replaceId(t.block) }
|
||||
}
|
||||
return t
|
||||
})
|
||||
} else if (targets && typeof targets === 'object' && (targets as any).block) {
|
||||
normalizedConnections[handle] = { ...targets, block: replaceId((targets as any).block) }
|
||||
} else {
|
||||
normalizedConnections[handle] = targets
|
||||
}
|
||||
}
|
||||
normalized.params.connections = normalizedConnections
|
||||
}
|
||||
|
||||
// Update nestedNodes block IDs
|
||||
if (normalized.params.nestedNodes) {
|
||||
const normalizedNestedNodes: Record<string, any> = {}
|
||||
for (const [childId, childBlock] of Object.entries(normalized.params.nestedNodes)) {
|
||||
const newChildId = replaceId(childId) ?? childId
|
||||
normalizedNestedNodes[newChildId] = childBlock
|
||||
}
|
||||
normalized.params.nestedNodes = normalizedNestedNodes
|
||||
}
|
||||
}
|
||||
|
||||
return normalized
|
||||
})
|
||||
|
||||
return { normalizedOperations, idMapping }
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply operations directly to the workflow JSON state
|
||||
*/
|
||||
@@ -1420,6 +1515,11 @@ function applyOperationsToWorkflowState(
|
||||
|
||||
// Log initial state
|
||||
const logger = createLogger('EditWorkflowServerTool')
|
||||
|
||||
// Normalize block IDs to UUIDs before processing
|
||||
const { normalizedOperations } = normalizeBlockIdsInOperations(operations)
|
||||
operations = normalizedOperations
|
||||
|
||||
logger.info('Applying operations to workflow:', {
|
||||
totalOperations: operations.length,
|
||||
operationTypes: operations.reduce((acc: any, op) => {
|
||||
|
||||
Reference in New Issue
Block a user