diff --git a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts index b45b207d5e4..4a32729626e 100644 --- a/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts +++ b/libs/designer-v2/src/lib/core/actions/bjsworkflow/serializer.ts @@ -287,7 +287,7 @@ export const serializeOperation = async ( } let serializedOperation: LogicAppsV2.OperationDefinition; - const isManagedMcpClient = operation.type?.toLowerCase() === 'mcpclienttool' && operation.kind?.toLowerCase() === "managed"; + const isManagedMcpClient = operation.type?.toLowerCase() === 'mcpclienttool' && operation.kind?.toLowerCase() === 'managed'; if (isManagedMcpClient) { serializedOperation = await serializeManagedMcpOperation(rootState, operationId); @@ -432,9 +432,10 @@ const serializeManifestBasedOperation = async (rootState: RootState, operationId const hostInfo = serializeHost(operationId, manifest, rootState); const inputs = hostInfo !== undefined ? mergeHostWithInputs(hostInfo, inputPathValue) : inputPathValue; const operationFromWorkflow = getRecordEntry(rootState.workflow.operations, operationId) as LogicAppsV2.OperationDefinition; - const runAfter = isRootNode(operationId, rootState.workflow.nodesMetadata) || manifest.properties.runAfter?.type === RunAfterType.NotSupported - ? undefined - : getRunAfter(operationFromWorkflow, idReplacements); + const runAfter = + isRootNode(operationId, rootState.workflow.nodesMetadata) || manifest.properties.runAfter?.type === RunAfterType.NotSupported + ? undefined + : getRunAfter(operationFromWorkflow, idReplacements); const recurrence = isTrigger && manifest.properties.recurrence && manifest.properties.recurrence.type !== RecurrenceType.None ? constructInputValues('recurrence.$.recurrence', inputsToSerialize, false /* encodePathComponents */) @@ -475,7 +476,7 @@ const serializeManagedMcpOperation = async (rootState: RootState, nodeId: string const nativeMcpOperationInfo = { connectorId: 'connectionProviders/mcpclient', operationId: 'nativemcpclient' }; const manifest = await getOperationManifest(nativeMcpOperationInfo); const inputParameters = serializeParametersFromManifest(inputsToSerialize, manifest); - + const operationFromWorkflow = getRecordEntry(rootState.workflow.operations, nodeId) as LogicAppsV2.OperationDefinition; const { parsedSwagger } = await getConnectorWithSwagger(connectorId); @@ -859,7 +860,7 @@ interface AgentConnectionInfo { interface McpConnectionInfo { connectionReference: { connectionName: string; - } + }; } const serializeHost = ( @@ -945,8 +946,8 @@ const serializeHost = ( case ConnectionReferenceKeyFormat.McpConnection: return { connectionReference: { - connectionName: referenceKey - } + connectionName: referenceKey, + }, }; default: throw new AssertionException( @@ -1006,28 +1007,28 @@ const serializeNestedOperations = async ( if (subGraphDetail?.allowOperations) { const operations = operationNodes.filter((graph) => graph.subGraphLocation === subGraphLocation); - const nestedOperationsPromises = operations.map((nestedOperation) => - serializeOperation(rootState, nestedOperation.id) - ) as Promise[]; - const nestedOperations = await Promise.all(nestedOperationsPromises); - const idReplacements = rootState.workflow.idReplacements; - - const newResult = {}; - safeSetObjectPropertyValue( - newResult, - [subGraphLocation], - nestedOperations.reduce((actions: LogicAppsV2.Actions, action: LogicAppsV2.OperationDefinition, index: number) => { - if (!isNullOrEmpty(action)) { - const actionId = operations[index].id; - actions[getRecordEntry(idReplacements, actionId) ?? actionId] = action; - return actions; - } + const nestedOperationsPromises = operations.map((nestedOperation) => + serializeOperation(rootState, nestedOperation.id) + ) as Promise[]; + const nestedOperations = await Promise.all(nestedOperationsPromises); + const idReplacements = rootState.workflow.idReplacements; + const newResult = {}; + safeSetObjectPropertyValue( + newResult, + [subGraphLocation], + nestedOperations.reduce((actions: LogicAppsV2.Actions, action: LogicAppsV2.OperationDefinition, index: number) => { + if (!isNullOrEmpty(action)) { + const actionId = operations[index].id; + actions[getRecordEntry(idReplacements, actionId) ?? actionId] = action; return actions; - }, {}) - ); + } + + return actions; + }, {}) + ); - result = merge(result, newResult); + result = merge(result, newResult); } if (subGraphDetail?.isAdditive) { diff --git a/libs/designer-v2/src/lib/core/parsers/BJSWorkflow/__test__/agentMcpWorkflowDefinition.ts b/libs/designer-v2/src/lib/core/parsers/BJSWorkflow/__test__/agentMcpWorkflowDefinition.ts index 8d2ff848ba1..45574867ed7 100644 --- a/libs/designer-v2/src/lib/core/parsers/BJSWorkflow/__test__/agentMcpWorkflowDefinition.ts +++ b/libs/designer-v2/src/lib/core/parsers/BJSWorkflow/__test__/agentMcpWorkflowDefinition.ts @@ -9,16 +9,16 @@ export const agentMcpWorkflowDefinitionInput = { contentVersion: '1.0.0.0', parameters: {}, triggers: { - 'manual': { + manual: { type: 'Request', kind: 'Http', inputs: { - schema: {} - } - } + schema: {}, + }, + }, }, actions: { - 'WorkflowAgent': { + WorkflowAgent: { type: 'Agent', inputs: { parameters: { @@ -26,45 +26,45 @@ export const agentMcpWorkflowDefinitionInput = { messages: [ { role: 'System', - content: 'You are a helpful assistant that can use tools to accomplish tasks.' + content: 'You are a helpful assistant that can use tools to accomplish tasks.', }, { role: 'User', - content: 'Help me manage files and data.' - } - ] - } + content: 'Help me manage files and data.', + }, + ], + }, }, tools: { - 'McpFileServer': { - type: 'McpClientTool', - kind: 'BuiltIn', - inputs: { - parameters: { - mcpServerPath: '/servers/filesystem', - toolName: 'list_files', - } - } - } + McpFileServer: { + type: 'McpClientTool', + kind: 'BuiltIn', + inputs: { + parameters: { + mcpServerPath: '/servers/filesystem', + toolName: 'list_files', + }, + }, + }, }, runAfter: {}, limit: { timeout: 'PT2H', - count: 50 - } + count: 50, + }, }, - 'ResponseAction': { + ResponseAction: { type: 'Response', inputs: { statusCode: 200, - body: '@outputs("WorkflowAgent")' + body: '@outputs("WorkflowAgent")', }, runAfter: { - 'WorkflowAgent': ['SUCCEEDED'] - } - } + WorkflowAgent: ['SUCCEEDED'], + }, + }, }, - outputs: {} + outputs: {}, }; export const expectedAgentMcpWorkflowDefinitionOutput = { @@ -92,16 +92,16 @@ export const expectedAgentMcpWorkflowDefinitionOutput = { children: [ { height: 40, - id: "WorkflowAgent-addCase-#subgraph", - type: "SUBGRAPH_CARD_NODE", + id: 'WorkflowAgent-addCase-#subgraph', + type: 'SUBGRAPH_CARD_NODE', width: 200, }, ], edges: [], - id: "WorkflowAgent-addCase", + id: 'WorkflowAgent-addCase', subGraphLocation: undefined, - type: "HIDDEN_NODE", - }, + type: 'HIDDEN_NODE', + }, ], edges: [ { @@ -124,15 +124,12 @@ export const expectedAgentMcpWorkflowDefinitionOutput = { }, createWorkflowNode('ResponseAction'), ], - edges: [ - createWorkflowEdge('manual', 'WorkflowAgent'), - createWorkflowEdge('WorkflowAgent', 'ResponseAction'), - ], + edges: [createWorkflowEdge('manual', 'WorkflowAgent'), createWorkflowEdge('WorkflowAgent', 'ResponseAction')], }, actionData: { manual: { inputs: { - schema: {} + schema: {}, }, kind: 'Http', type: 'Request', @@ -144,30 +141,30 @@ export const expectedAgentMcpWorkflowDefinitionOutput = { messages: [ { role: 'System', - content: 'You are a helpful assistant that can use tools to accomplish tasks.' + content: 'You are a helpful assistant that can use tools to accomplish tasks.', }, { role: 'User', - content: 'Help me manage files and data.' - } - ] - } + content: 'Help me manage files and data.', + }, + ], + }, }, tools: { - 'McpFileServer': { - type: 'McpClientTool', - kind: 'BuiltIn', - inputs: { - parameters: { - mcpServerPath: '/servers/filesystem', - toolName: 'list_files', - } - } - } + McpFileServer: { + type: 'McpClientTool', + kind: 'BuiltIn', + inputs: { + parameters: { + mcpServerPath: '/servers/filesystem', + toolName: 'list_files', + }, + }, + }, }, limit: { timeout: 'PT2H', - count: 50 + count: 50, }, runAfter: {}, type: 'Agent', @@ -175,27 +172,27 @@ export const expectedAgentMcpWorkflowDefinitionOutput = { ResponseAction: { inputs: { statusCode: 200, - body: '@outputs("WorkflowAgent")' + body: '@outputs("WorkflowAgent")', }, runAfter: { - 'WorkflowAgent': ['SUCCEEDED'] + WorkflowAgent: ['SUCCEEDED'], }, type: 'Response', }, McpFileServer: { - type: 'McpClientTool', - kind: 'BuiltIn', - inputs: { - parameters: { - mcpServerPath: '/servers/filesystem', - toolName: 'list_files', - } - } + type: 'McpClientTool', + kind: 'BuiltIn', + inputs: { + parameters: { + mcpServerPath: '/servers/filesystem', + toolName: 'list_files', + }, + }, }, }, nodesMetadata: { manual: { graphId: 'root', isRoot: true, isTrigger: true }, - WorkflowAgent: { + WorkflowAgent: { graphId: 'root', actionCount: 1, parentNodeId: undefined, @@ -204,13 +201,13 @@ export const expectedAgentMcpWorkflowDefinitionOutput = { McpFileServer: { graphId: 'WorkflowAgent', parentNodeId: 'WorkflowAgent', - subgraphType: "MCP_CLIENT", + subgraphType: 'MCP_CLIENT', }, 'WorkflowAgent-addCase': { - actionCount: 0, - graphId: 'WorkflowAgent', - parentNodeId: 'WorkflowAgent', - subgraphType: 'AGENT_ADD_CONDITON', + actionCount: 0, + graphId: 'WorkflowAgent', + parentNodeId: 'WorkflowAgent', + subgraphType: 'AGENT_ADD_CONDITON', }, }, -}; \ No newline at end of file +}; diff --git a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx index ce98910dabf..1e2a3ca4223 100644 --- a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx +++ b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx @@ -52,5 +52,46 @@ describe('CreateConnectionWrapper', () => { expectedConnectionParameterSetValuesWithUndefined ); }); + + it('filters out parameters not in validParameterKeys when provided', () => { + const outputValues = { + parameter1: 'value1', + parameter2: 'value2', + 'token:tenantId': 'tenant-id-value', // This should be filtered out + extraParam: 'extra-value', // This should be filtered out + }; + + const validKeys = ['parameter1', 'parameter2']; + + const result = getConnectionParameterSetValues(selectedParameterSetName, outputValues, validKeys); + + expect(result).toEqual({ + name: selectedParameterSetName, + values: { + parameter1: { value: 'value1' }, + parameter2: { value: 'value2' }, + }, + }); + // Ensure filtered parameters are not included + expect(result.values).not.toHaveProperty('token:tenantId'); + expect(result.values).not.toHaveProperty('extraParam'); + }); + + it('includes all parameters when validParameterKeys is not provided', () => { + const outputValues = { + parameter1: 'value1', + 'token:tenantId': 'tenant-id-value', + }; + + const result = getConnectionParameterSetValues(selectedParameterSetName, outputValues); + + expect(result).toEqual({ + name: selectedParameterSetName, + values: { + parameter1: { value: 'value1' }, + 'token:tenantId': { value: 'tenant-id-value' }, + }, + }); + }); }); }); diff --git a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx index 300f94630b2..b887ed21b22 100644 --- a/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx +++ b/libs/designer-v2/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx @@ -197,7 +197,11 @@ export const CreateConnectionInternal = (props: { const connectionInfo: ConnectionCreationInfo = { displayName, connectionParametersSet: selectedParameterSet - ? getConnectionParameterSetValues(selectedParameterSet.name, outputParameterValues) + ? getConnectionParameterSetValues( + selectedParameterSet.name, + outputParameterValues, + Object.keys(selectedParameterSet.parameters ?? {}) + ) : undefined, connectionParameters: outputParameterValues, alternativeParameterValues, @@ -322,14 +326,21 @@ export const CreateConnectionInternal = (props: { export function getConnectionParameterSetValues( selectedParameterSetName: string, - outputParameterValues: Record + outputParameterValues: Record, + validParameterKeys?: string[] ): ConnectionParameterSetValues { + // Filter to only include parameters that are defined in the parameter set + // This prevents sending parameters that the connector API doesn't recognize + const filteredParameterValues = validParameterKeys + ? Object.fromEntries(Object.entries(outputParameterValues).filter(([key]) => validParameterKeys.includes(key))) + : outputParameterValues; + return { name: selectedParameterSetName, - values: Object.keys(outputParameterValues).reduce((acc: any, key) => { + values: Object.keys(filteredParameterValues).reduce((acc: any, key) => { // eslint-disable-next-line no-param-reassign acc[key] = { - value: outputParameterValues[key], + value: filteredParameterValues[key], }; return acc; }, {}), diff --git a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx index ce98910dabf..1e2a3ca4223 100644 --- a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx +++ b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/__test__/createConnectionWrapper.spec.tsx @@ -52,5 +52,46 @@ describe('CreateConnectionWrapper', () => { expectedConnectionParameterSetValuesWithUndefined ); }); + + it('filters out parameters not in validParameterKeys when provided', () => { + const outputValues = { + parameter1: 'value1', + parameter2: 'value2', + 'token:tenantId': 'tenant-id-value', // This should be filtered out + extraParam: 'extra-value', // This should be filtered out + }; + + const validKeys = ['parameter1', 'parameter2']; + + const result = getConnectionParameterSetValues(selectedParameterSetName, outputValues, validKeys); + + expect(result).toEqual({ + name: selectedParameterSetName, + values: { + parameter1: { value: 'value1' }, + parameter2: { value: 'value2' }, + }, + }); + // Ensure filtered parameters are not included + expect(result.values).not.toHaveProperty('token:tenantId'); + expect(result.values).not.toHaveProperty('extraParam'); + }); + + it('includes all parameters when validParameterKeys is not provided', () => { + const outputValues = { + parameter1: 'value1', + 'token:tenantId': 'tenant-id-value', + }; + + const result = getConnectionParameterSetValues(selectedParameterSetName, outputValues); + + expect(result).toEqual({ + name: selectedParameterSetName, + values: { + parameter1: { value: 'value1' }, + 'token:tenantId': { value: 'tenant-id-value' }, + }, + }); + }); }); }); diff --git a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx index 10495f4c395..4759c2bb2ce 100644 --- a/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx +++ b/libs/designer/src/lib/ui/panel/connectionsPanel/createConnection/createConnectionInternal.tsx @@ -199,7 +199,11 @@ export const CreateConnectionInternal = (props: { const connectionInfo: ConnectionCreationInfo = { displayName, connectionParametersSet: selectedParameterSet - ? getConnectionParameterSetValues(selectedParameterSet.name, outputParameterValues) + ? getConnectionParameterSetValues( + selectedParameterSet.name, + outputParameterValues, + Object.keys(selectedParameterSet.parameters ?? {}) + ) : undefined, connectionParameters: outputParameterValues, alternativeParameterValues, @@ -333,14 +337,21 @@ export const CreateConnectionInternal = (props: { export function getConnectionParameterSetValues( selectedParameterSetName: string, - outputParameterValues: Record + outputParameterValues: Record, + validParameterKeys?: string[] ): ConnectionParameterSetValues { + // Filter to only include parameters that are defined in the parameter set + // This prevents sending parameters that the connector API doesn't recognize + const filteredParameterValues = validParameterKeys + ? Object.fromEntries(Object.entries(outputParameterValues).filter(([key]) => validParameterKeys.includes(key))) + : outputParameterValues; + return { name: selectedParameterSetName, - values: Object.keys(outputParameterValues).reduce((acc: any, key) => { + values: Object.keys(filteredParameterValues).reduce((acc: any, key) => { // eslint-disable-next-line no-param-reassign acc[key] = { - value: outputParameterValues[key], + value: filteredParameterValues[key], }; return acc; }, {}),