From d05e10ab0718cd2ccd6242cb331a27a366265afc Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 10 Feb 2026 13:26:54 -0800 Subject: [PATCH 1/7] improvement(mcp): improved mcp sse events notifs, update jira to handle files, fix UI issues in settings modal, fix org and workspace invitations when bundled --- apps/docs/content/docs/en/tools/jira.mdx | 37 +- .../mcp/workflow-servers/[id]/tools/route.ts | 8 +- .../sim/app/api/mcp/workflow-servers/route.ts | 5 +- .../[id]/invitations/[invitationId]/route.ts | 40 +- .../api/tools/jira/add-attachment/route.ts | 9 +- .../deploy-modal/components/mcp/mcp.tsx | 206 +++++----- .../components/tool-credential-selector.tsx | 11 +- .../components/tool-input/tool-input.tsx | 10 +- .../components/custom-tools/custom-tools.tsx | 10 +- .../settings-modal/components/mcp/mcp.tsx | 378 +++++++++++++++++- .../skills/components/skill-modal.tsx | 28 +- .../components/skills/skills.tsx | 22 +- .../member-invitation-card.tsx | 5 +- apps/sim/executor/execution/block-executor.ts | 4 +- apps/sim/hooks/queries/mcp.ts | 4 + .../tool-executor/deployment-tools/deploy.ts | 12 +- .../tool-executor/deployment-tools/manage.ts | 9 +- apps/sim/lib/mcp/workflow-mcp-sync.ts | 91 ++++- apps/sim/tools/jira/add_attachment.ts | 14 +- apps/sim/tools/jira/add_comment.ts | 3 +- apps/sim/tools/jira/add_watcher.ts | 3 +- apps/sim/tools/jira/add_worklog.ts | 3 +- apps/sim/tools/jira/assign_issue.ts | 3 +- apps/sim/tools/jira/create_issue_link.ts | 5 +- apps/sim/tools/jira/delete_attachment.ts | 3 +- apps/sim/tools/jira/delete_comment.ts | 3 +- apps/sim/tools/jira/delete_issue.ts | 3 +- apps/sim/tools/jira/delete_issue_link.ts | 3 +- apps/sim/tools/jira/delete_worklog.ts | 3 +- apps/sim/tools/jira/get_attachments.ts | 24 +- apps/sim/tools/jira/get_comments.ts | 1 + apps/sim/tools/jira/get_users.ts | 16 +- apps/sim/tools/jira/get_worklogs.ts | 1 + apps/sim/tools/jira/remove_watcher.ts | 3 +- apps/sim/tools/jira/retrieve.ts | 36 +- apps/sim/tools/jira/search_issues.ts | 2 + apps/sim/tools/jira/transition_issue.ts | 3 +- apps/sim/tools/jira/types.ts | 181 ++++++++- apps/sim/tools/jira/update.ts | 3 +- apps/sim/tools/jira/update_comment.ts | 3 +- apps/sim/tools/jira/update_worklog.ts | 3 +- apps/sim/tools/jira/utils.ts | 45 +++ apps/sim/tools/jira/write.ts | 3 +- 43 files changed, 987 insertions(+), 272 deletions(-) diff --git a/apps/docs/content/docs/en/tools/jira.mdx b/apps/docs/content/docs/en/tools/jira.mdx index 179d7023a3..df97c0957c 100644 --- a/apps/docs/content/docs/en/tools/jira.mdx +++ b/apps/docs/content/docs/en/tools/jira.mdx @@ -44,6 +44,7 @@ Retrieve detailed information about a specific Jira issue | --------- | ---- | -------- | ----------- | | `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) | | `issueKey` | string | Yes | Jira issue key to retrieve \(e.g., PROJ-123\) | +| `includeAttachments` | boolean | No | Download attachment file contents and include them as files in the output | | `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. | #### Output @@ -65,6 +66,7 @@ Retrieve detailed information about a specific Jira issue | ↳ `key` | string | Status category key \(e.g., new, indeterminate, done\) | | ↳ `name` | string | Status category name \(e.g., To Do, In Progress, Done\) | | ↳ `colorName` | string | Status category color \(e.g., blue-gray, yellow, green\) | +| `statusName` | string | Issue status name \(e.g., Open, In Progress, Done\) | | `issuetype` | object | Issue type | | ↳ `id` | string | Issue type ID | | ↳ `name` | string | Issue type name \(e.g., Task, Bug, Story, Epic\) | @@ -88,6 +90,7 @@ Retrieve detailed information about a specific Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| `assigneeName` | string | Assignee display name or account ID | | `reporter` | object | Reporter user | | ↳ `accountId` | string | Atlassian account ID of the user | | ↳ `displayName` | string | Display name of the user | @@ -173,6 +176,7 @@ Retrieve detailed information about a specific Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `authorName` | string | Comment author display name | | ↳ `updateAuthor` | object | User who last updated the comment | | ↳ `accountId` | string | Atlassian account ID of the user | | ↳ `displayName` | string | Display name of the user | @@ -196,6 +200,7 @@ Retrieve detailed information about a specific Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `authorName` | string | Worklog author display name | | ↳ `updateAuthor` | object | User who last updated the worklog | | ↳ `accountId` | string | Atlassian account ID of the user | | ↳ `displayName` | string | Display name of the user | @@ -225,9 +230,11 @@ Retrieve detailed information about a specific Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `authorName` | string | Attachment author display name | | ↳ `created` | string | ISO 8601 timestamp when the attachment was created | | `issueKey` | string | Issue key \(e.g., PROJ-123\) | | `issue` | json | Complete raw Jira issue object from the API | +| `files` | file[] | Downloaded attachment files \(only when includeAttachments is true\) | ### `jira_update` @@ -258,6 +265,7 @@ Update a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Updated issue key \(e.g., PROJ-123\) | | `summary` | string | Issue summary after update | @@ -296,6 +304,7 @@ Create a new Jira issue | `issueKey` | string | Created issue key \(e.g., PROJ-123\) | | `self` | string | REST API URL for the created issue | | `summary` | string | Issue summary | +| `success` | boolean | Whether the issue was created successfully | | `url` | string | URL to the created issue in Jira | | `assigneeId` | string | Account ID of the assigned user \(null if no assignee was set\) | @@ -358,6 +367,7 @@ Delete a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Deleted issue key | ### `jira_assign_issue` @@ -378,6 +388,7 @@ Assign a Jira issue to a user | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key that was assigned | | `assigneeId` | string | Account ID of the assignee \(use "-1" for auto-assign, null to unassign\) | @@ -401,6 +412,7 @@ Move a Jira issue between workflow statuses (e.g., To Do -> In Progress) | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key that was transitioned | | `transitionId` | string | Applied transition ID | | `transitionName` | string | Applied transition name | @@ -443,6 +455,7 @@ Search for Jira issues using JQL (Jira Query Language) | ↳ `key` | string | Status category key \(e.g., new, indeterminate, done\) | | ↳ `name` | string | Status category name \(e.g., To Do, In Progress, Done\) | | ↳ `colorName` | string | Status category color \(e.g., blue-gray, yellow, green\) | +| ↳ `statusName` | string | Issue status name \(e.g., Open, In Progress, Done\) | | ↳ `issuetype` | object | Issue type | | ↳ `id` | string | Issue type ID | | ↳ `name` | string | Issue type name \(e.g., Task, Bug, Story, Epic\) | @@ -466,6 +479,7 @@ Search for Jira issues using JQL (Jira Query Language) | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `assigneeName` | string | Assignee display name or account ID | | ↳ `reporter` | object | Reporter user | | ↳ `accountId` | string | Atlassian account ID of the user | | ↳ `displayName` | string | Display name of the user | @@ -509,6 +523,7 @@ Add a comment to a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key the comment was added to | | `commentId` | string | Created comment ID | | `body` | string | Comment text content | @@ -558,6 +573,7 @@ Get all comments from a Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `authorName` | string | Comment author display name | | ↳ `updateAuthor` | object | User who last updated the comment | | ↳ `accountId` | string | Atlassian account ID of the user | | ↳ `displayName` | string | Display name of the user | @@ -592,6 +608,7 @@ Update an existing comment on a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key | | `commentId` | string | Updated comment ID | | `body` | string | Updated comment text | @@ -624,6 +641,7 @@ Delete a comment from a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key | | `commentId` | string | Deleted comment ID | @@ -637,6 +655,7 @@ Get all attachments from a Jira issue | --------- | ---- | -------- | ----------- | | `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) | | `issueKey` | string | Yes | Jira issue key to get attachments from \(e.g., PROJ-123\) | +| `includeAttachments` | boolean | No | Download attachment file contents and include them as files in the output | | `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. | #### Output @@ -660,7 +679,9 @@ Get all attachments from a Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `authorName` | string | Attachment author display name | | ↳ `created` | string | ISO 8601 timestamp when the attachment was created | +| `files` | file[] | Downloaded attachment files \(only when includeAttachments is true\) | ### `jira_add_attachment` @@ -688,10 +709,7 @@ Add attachments to a Jira issue | ↳ `size` | number | File size in bytes | | ↳ `content` | string | URL to download the attachment | | `attachmentIds` | array | Array of attachment IDs | -| `files` | array | Uploaded file metadata | -| ↳ `name` | string | File name | -| ↳ `mimeType` | string | MIME type | -| ↳ `size` | number | File size in bytes | +| `files` | file[] | Uploaded attachment files | ### `jira_delete_attachment` @@ -710,6 +728,7 @@ Delete an attachment from a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `attachmentId` | string | Deleted attachment ID | ### `jira_add_worklog` @@ -733,6 +752,7 @@ Add a time tracking worklog entry to a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key the worklog was added to | | `worklogId` | string | Created worklog ID | | `timeSpent` | string | Time spent in human-readable format \(e.g., 3h 20m\) | @@ -781,6 +801,7 @@ Get all worklog entries from a Jira issue | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `authorName` | string | Worklog author display name | | ↳ `updateAuthor` | object | User who last updated the worklog | | ↳ `accountId` | string | Atlassian account ID of the user | | ↳ `displayName` | string | Display name of the user | @@ -818,6 +839,7 @@ Update an existing worklog entry on a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key | | `worklogId` | string | Updated worklog ID | | `timeSpent` | string | Human-readable time spent \(e.g., "3h 20m"\) | @@ -861,6 +883,7 @@ Delete a worklog entry from a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key | | `worklogId` | string | Deleted worklog ID | @@ -884,6 +907,7 @@ Create a link relationship between two Jira issues | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `inwardIssue` | string | Inward issue key | | `outwardIssue` | string | Outward issue key | | `linkType` | string | Type of issue link | @@ -906,6 +930,7 @@ Delete a link between two Jira issues | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `linkId` | string | Deleted link ID | ### `jira_add_watcher` @@ -926,6 +951,7 @@ Add a watcher to a Jira issue to receive notifications about updates | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key | | `watcherAccountId` | string | Added watcher account ID | @@ -947,6 +973,7 @@ Remove a watcher from a Jira issue | Parameter | Type | Description | | --------- | ---- | ----------- | | `ts` | string | ISO 8601 timestamp of the operation | +| `success` | boolean | Operation success status | | `issueKey` | string | Issue key | | `watcherAccountId` | string | Removed watcher account ID | @@ -977,6 +1004,8 @@ Get Jira users. If an account ID is provided, returns a single user. Otherwise, | ↳ `accountType` | string | Type of account \(e.g., atlassian, app, customer\) | | ↳ `avatarUrl` | string | URL to the user avatar \(48x48\) | | ↳ `timeZone` | string | User timezone | +| ↳ `avatarUrls` | json | User avatar URLs in multiple sizes \(16x16, 24x24, 32x32, 48x48\) | +| ↳ `self` | string | REST API URL for this user | | `total` | number | Total number of users returned | | `startAt` | number | Pagination start index | | `maxResults` | number | Maximum results per page | diff --git a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts index 6705d52989..54b73fe867 100644 --- a/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/[id]/tools/route.ts @@ -6,6 +6,7 @@ import type { NextRequest } from 'next/server' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpPubSub } from '@/lib/mcp/pubsub' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' +import { generateParameterSchemaForWorkflow } from '@/lib/mcp/workflow-mcp-sync' import { sanitizeToolName } from '@/lib/mcp/workflow-tool-schema' import { hasValidStartBlock } from '@/lib/workflows/triggers/trigger-utils.server' @@ -170,6 +171,11 @@ export const POST = withMcpAuth('write')( workflowRecord.description || `Execute ${workflowRecord.name} workflow` + const parameterSchema = + body.parameterSchema && Object.keys(body.parameterSchema).length > 0 + ? body.parameterSchema + : await generateParameterSchemaForWorkflow(body.workflowId) + const toolId = crypto.randomUUID() const [tool] = await db .insert(workflowMcpTool) @@ -179,7 +185,7 @@ export const POST = withMcpAuth('write')( workflowId: body.workflowId, toolName, toolDescription, - parameterSchema: body.parameterSchema || {}, + parameterSchema, createdAt: new Date(), updatedAt: new Date(), }) diff --git a/apps/sim/app/api/mcp/workflow-servers/route.ts b/apps/sim/app/api/mcp/workflow-servers/route.ts index 1779e51a9b..12c9de3910 100644 --- a/apps/sim/app/api/mcp/workflow-servers/route.ts +++ b/apps/sim/app/api/mcp/workflow-servers/route.ts @@ -6,6 +6,7 @@ import type { NextRequest } from 'next/server' import { getParsedBody, withMcpAuth } from '@/lib/mcp/middleware' import { mcpPubSub } from '@/lib/mcp/pubsub' import { createMcpErrorResponse, createMcpSuccessResponse } from '@/lib/mcp/utils' +import { generateParameterSchemaForWorkflow } from '@/lib/mcp/workflow-mcp-sync' import { sanitizeToolName } from '@/lib/mcp/workflow-tool-schema' import { hasValidStartBlock } from '@/lib/workflows/triggers/trigger-utils.server' @@ -156,6 +157,8 @@ export const POST = withMcpAuth('write')( const toolDescription = workflowRecord.description || `Execute ${workflowRecord.name} workflow` + const parameterSchema = await generateParameterSchemaForWorkflow(workflowRecord.id) + const toolId = crypto.randomUUID() await db.insert(workflowMcpTool).values({ id: toolId, @@ -163,7 +166,7 @@ export const POST = withMcpAuth('write')( workflowId: workflowRecord.id, toolName, toolDescription, - parameterSchema: {}, + parameterSchema, createdAt: new Date(), updatedAt: new Date(), }) diff --git a/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts b/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts index cd716fe15d..86c1b91efe 100644 --- a/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts +++ b/apps/sim/app/api/organizations/[id]/invitations/[invitationId]/route.ts @@ -446,15 +446,37 @@ export async function PUT( }) .where(eq(workspaceInvitation.id, wsInvitation.id)) - await tx.insert(permissions).values({ - id: randomUUID(), - entityType: 'workspace', - entityId: wsInvitation.workspaceId, - userId: session.user.id, - permissionType: wsInvitation.permissions || 'read', - createdAt: new Date(), - updatedAt: new Date(), - }) + const existingPermission = await tx + .select({ id: permissions.id }) + .from(permissions) + .where( + and( + eq(permissions.entityId, wsInvitation.workspaceId), + eq(permissions.entityType, 'workspace'), + eq(permissions.userId, session.user.id) + ) + ) + .then((rows) => rows[0]) + + if (existingPermission) { + await tx + .update(permissions) + .set({ + permissionType: wsInvitation.permissions || 'read', + updatedAt: new Date(), + }) + .where(eq(permissions.id, existingPermission.id)) + } else { + await tx.insert(permissions).values({ + id: randomUUID(), + entityType: 'workspace', + entityId: wsInvitation.workspaceId, + userId: session.user.id, + permissionType: wsInvitation.permissions || 'read', + createdAt: new Date(), + updatedAt: new Date(), + }) + } } } else if (status === 'cancelled') { await tx diff --git a/apps/sim/app/api/tools/jira/add-attachment/route.ts b/apps/sim/app/api/tools/jira/add-attachment/route.ts index 63b031032a..110a915703 100644 --- a/apps/sim/app/api/tools/jira/add-attachment/route.ts +++ b/apps/sim/app/api/tools/jira/add-attachment/route.ts @@ -47,16 +47,9 @@ export async function POST(request: NextRequest) { (await getJiraCloudId(validatedData.domain, validatedData.accessToken)) const formData = new FormData() - const filesOutput: Array<{ name: string; mimeType: string; data: string; size: number }> = [] for (const file of userFiles) { const buffer = await downloadFileFromStorage(file, requestId, logger) - filesOutput.push({ - name: file.name, - mimeType: file.type || 'application/octet-stream', - data: buffer.toString('base64'), - size: buffer.length, - }) const blob = new Blob([new Uint8Array(buffer)], { type: file.type || 'application/octet-stream', }) @@ -109,7 +102,7 @@ export async function POST(request: NextRequest) { issueKey: validatedData.issueKey, attachments, attachmentIds, - files: filesOutput, + files: userFiles, }, }) } catch (error) { diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx index a3b3206ea6..f9f6faa3c3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx @@ -45,6 +45,12 @@ interface McpDeployProps { onCanSaveChange?: (canSave: boolean) => void } +function haveSameServerSelection(a: string[], b: string[]): boolean { + if (a.length !== b.length) return false + const bSet = new Set(b) + return a.every((id) => bSet.has(id)) +} + /** * Generate JSON Schema from input format with optional descriptions */ @@ -179,6 +185,7 @@ export function McpDeploy({ } return ids }, [servers, serverToolsMap]) + const [draftSelectedServerIds, setDraftSelectedServerIds] = useState(null) const hasLoadedInitialData = useRef(false) @@ -238,9 +245,10 @@ export function McpDeploy({ } }, [toolName, toolDescription, parameterDescriptions, savedValues]) - const hasDeployedTools = selectedServerIds.length > 0 - const hasChanges = useMemo(() => { - if (!savedValues || !hasDeployedTools) return false + const selectedServerIdsForForm = draftSelectedServerIds ?? selectedServerIds + + const hasToolConfigurationChanges = useMemo(() => { + if (!savedValues) return false if (toolName !== savedValues.toolName) return true if (toolDescription !== savedValues.toolDescription) return true if ( @@ -249,11 +257,18 @@ export function McpDeploy({ return true } return false - }, [toolName, toolDescription, parameterDescriptions, hasDeployedTools, savedValues]) + }, [toolName, toolDescription, parameterDescriptions, savedValues]) + const hasServerSelectionChanges = useMemo( + () => !haveSameServerSelection(selectedServerIdsForForm, selectedServerIds), + [selectedServerIdsForForm, selectedServerIds] + ) + const hasChanges = + hasServerSelectionChanges || + (hasToolConfigurationChanges && selectedServerIdsForForm.length > 0) useEffect(() => { - onCanSaveChange?.(hasChanges && hasDeployedTools && !!toolName.trim()) - }, [hasChanges, hasDeployedTools, toolName, onCanSaveChange]) + onCanSaveChange?.(hasChanges && !!toolName.trim()) + }, [hasChanges, toolName, onCanSaveChange]) /** * Save tool configuration to all deployed servers @@ -261,28 +276,80 @@ export function McpDeploy({ const handleSave = useCallback(async () => { if (!toolName.trim()) return - const toolsToUpdate: Array<{ serverId: string; toolId: string }> = [] - for (const server of servers) { - const toolInfo = serverToolsMap[server.id] - if (toolInfo?.tool) { - toolsToUpdate.push({ serverId: server.id, toolId: toolInfo.tool.id }) - } - } + const currentIds = new Set(selectedServerIds) + const nextIds = new Set(selectedServerIdsForForm) + const toAdd = selectedServerIdsForForm.filter((id) => !currentIds.has(id)) + const toRemove = selectedServerIds.filter((id) => !nextIds.has(id)) + const shouldUpdateExisting = hasToolConfigurationChanges - if (toolsToUpdate.length === 0) return + if (toAdd.length === 0 && toRemove.length === 0 && !shouldUpdateExisting) return onSubmittingChange?.(true) try { - for (const { serverId, toolId } of toolsToUpdate) { - await updateToolMutation.mutateAsync({ - workspaceId, - serverId, - toolId, - toolName: toolName.trim(), - toolDescription: toolDescription.trim() || undefined, - parameterSchema, - }) + const nextServerToolsMap = { ...serverToolsMap } + + for (const serverId of toAdd) { + setPendingServerChanges((prev) => new Set(prev).add(serverId)) + try { + const addedTool = await addToolMutation.mutateAsync({ + workspaceId, + serverId, + workflowId, + toolName: toolName.trim(), + toolDescription: toolDescription.trim() || undefined, + parameterSchema, + }) + nextServerToolsMap[serverId] = { tool: addedTool, isLoading: false } + onAddedToServer?.() + logger.info(`Added workflow ${workflowId} as tool to server ${serverId}`) + } finally { + setPendingServerChanges((prev) => { + const next = new Set(prev) + next.delete(serverId) + return next + }) + } } + + for (const serverId of toRemove) { + const toolInfo = nextServerToolsMap[serverId] + if (!toolInfo?.tool) continue + + setPendingServerChanges((prev) => new Set(prev).add(serverId)) + try { + await deleteToolMutation.mutateAsync({ + workspaceId, + serverId, + toolId: toolInfo.tool.id, + }) + delete nextServerToolsMap[serverId] + } finally { + setPendingServerChanges((prev) => { + const next = new Set(prev) + next.delete(serverId) + return next + }) + } + } + + if (shouldUpdateExisting) { + for (const serverId of selectedServerIdsForForm) { + const toolInfo = nextServerToolsMap[serverId] + if (!toolInfo?.tool) continue + + await updateToolMutation.mutateAsync({ + workspaceId, + serverId, + toolId: toolInfo.tool.id, + toolName: toolName.trim(), + toolDescription: toolDescription.trim() || undefined, + parameterSchema, + }) + } + } + + setServerToolsMap(nextServerToolsMap) + setDraftSelectedServerIds(null) // Update saved values after successful save (triggers re-render → hasChanges becomes false) setSavedValues({ toolName, @@ -300,10 +367,16 @@ export function McpDeploy({ toolDescription, parameterDescriptions, parameterSchema, - servers, + selectedServerIds, + selectedServerIdsForForm, + hasToolConfigurationChanges, serverToolsMap, workspaceId, + workflowId, + addToolMutation, + deleteToolMutation, updateToolMutation, + onAddedToServer, onSubmittingChange, onCanSaveChange, ]) @@ -315,90 +388,19 @@ export function McpDeploy({ })) }, [servers]) - const handleServerSelectionChange = useCallback( - async (newSelectedIds: string[]) => { - if (!toolName.trim()) return - - const currentIds = new Set(selectedServerIds) - const newIds = new Set(newSelectedIds) - - const toAdd = newSelectedIds.filter((id) => !currentIds.has(id)) - const toRemove = selectedServerIds.filter((id) => !newIds.has(id)) - - for (const serverId of toAdd) { - setPendingServerChanges((prev) => new Set(prev).add(serverId)) - try { - await addToolMutation.mutateAsync({ - workspaceId, - serverId, - workflowId, - toolName: toolName.trim(), - toolDescription: toolDescription.trim() || undefined, - parameterSchema, - }) - onAddedToServer?.() - logger.info(`Added workflow ${workflowId} as tool to server ${serverId}`) - } catch (error) { - logger.error('Failed to add tool:', error) - } finally { - setPendingServerChanges((prev) => { - const next = new Set(prev) - next.delete(serverId) - return next - }) - } - } - - for (const serverId of toRemove) { - const toolInfo = serverToolsMap[serverId] - if (toolInfo?.tool) { - setPendingServerChanges((prev) => new Set(prev).add(serverId)) - try { - await deleteToolMutation.mutateAsync({ - workspaceId, - serverId, - toolId: toolInfo.tool.id, - }) - setServerToolsMap((prev) => { - const next = { ...prev } - delete next[serverId] - return next - }) - } catch (error) { - logger.error('Failed to remove tool:', error) - } finally { - setPendingServerChanges((prev) => { - const next = new Set(prev) - next.delete(serverId) - return next - }) - } - } - } - }, - [ - selectedServerIds, - serverToolsMap, - toolName, - toolDescription, - workspaceId, - workflowId, - parameterSchema, - addToolMutation, - deleteToolMutation, - onAddedToServer, - ] - ) + const handleServerSelectionChange = useCallback((newSelectedIds: string[]) => { + setDraftSelectedServerIds(newSelectedIds) + }, []) const selectedServersLabel = useMemo(() => { - const count = selectedServerIds.length + const count = selectedServerIdsForForm.length if (count === 0) return 'Select servers...' if (count === 1) { - const server = servers.find((s) => s.id === selectedServerIds[0]) + const server = servers.find((s) => s.id === selectedServerIdsForForm[0]) return server?.name || '1 server' } return `${count} servers selected` - }, [selectedServerIds, servers]) + }, [selectedServerIdsForForm, servers]) const isPending = pendingServerChanges.size > 0 @@ -544,7 +546,7 @@ export function McpDeploy({ { - if (!isEditing) { - setInputValue(resolvedLabel) - } - }, [resolvedLabel, isEditing]) + const inputValue = isEditing ? editingInputValue : resolvedLabel const invalidSelection = Boolean(selectedId) && @@ -189,13 +185,12 @@ export function ToolCredentialSelector({ const matchedCred = credentials.find((c) => c.id === newValue) if (matchedCred) { - setInputValue(matchedCred.name) handleSelect(newValue) return } setIsEditing(true) - setInputValue(newValue) + setEditingInputValue(newValue) }, [credentials, handleAddCredential, handleSelect] ) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx index 9990c3eebb..6bf360d6aa 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/tool-input.tsx @@ -62,12 +62,7 @@ import { type CustomTool as CustomToolDefinition, useCustomTools, } from '@/hooks/queries/custom-tools' -import { - useForceRefreshMcpTools, - useMcpServers, - useMcpToolsEvents, - useStoredMcpTools, -} from '@/hooks/queries/mcp' +import { useForceRefreshMcpTools, useMcpServers, useStoredMcpTools } from '@/hooks/queries/mcp' import { useChildDeploymentStatus, useDeployChildWorkflow, @@ -1040,7 +1035,6 @@ export const ToolInput = memo(function ToolInput({ const { data: mcpServers = [], isLoading: mcpServersLoading } = useMcpServers(workspaceId) const { data: storedMcpTools = [] } = useStoredMcpTools(workspaceId) const forceRefreshMcpTools = useForceRefreshMcpTools() - useMcpToolsEvents(workspaceId) const openSettingsModal = useSettingsModalStore((state) => state.openModal) const mcpDataLoading = mcpLoading || mcpServersLoading @@ -2642,7 +2636,7 @@ export const ToolInput = memo(function ToolInput({ {!isCustomTool && isExpandedForDisplay && ( -
+
{/* Operation dropdown for tools with multiple operations */} {(() => { const hasOperations = hasMultipleOperations(tool.type) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/custom-tools/custom-tools.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/custom-tools/custom-tools.tsx index 33ee8340ad..42d2f9f4dd 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/custom-tools/custom-tools.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/custom-tools/custom-tools.tsx @@ -6,7 +6,6 @@ import { Plus, Search } from 'lucide-react' import { useParams } from 'next/navigation' import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn' import { Input, Skeleton } from '@/components/ui' -import { cn } from '@/lib/core/utils/cn' import { CustomToolModal } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/tool-input/components/custom-tool-modal/custom-tool-modal' import { useCustomTools, useDeleteCustomTool } from '@/hooks/queries/custom-tools' @@ -103,12 +102,7 @@ export function CustomTools() { <>
-
+
setSearchTerm(e.target.value)} disabled={isLoading} - className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-100' + className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' />
- +
+ + +
+ + + + Edit MCP Server + +
+ + { + if (editTestResult) clearEditTestResult() + setEditFormData((prev) => ({ ...prev, name: e.target.value })) + }} + className='h-9' + /> + + + + handleEditInputChange('url', e.target.value)} + onScroll={setEditUrlScrollLeft} + /> + + +
+
+ + Headers + + +
+ +
+ {(editFormData.headers || []).map((header, index) => ( + handleEditRemoveHeader(index)} + /> + ))} +
+
+
+
+ +
+ +
+ + +
+
+
+
+
) } diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx index 99a473fd2b..190b823203 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal.tsx @@ -1,7 +1,7 @@ 'use client' import type { ChangeEvent } from 'react' -import { useEffect, useMemo, useState } from 'react' +import { useMemo, useState } from 'react' import { useParams } from 'next/navigation' import { Button, @@ -52,21 +52,17 @@ export function SkillModal({ const [content, setContent] = useState('') const [errors, setErrors] = useState({}) const [saving, setSaving] = useState(false) - - useEffect(() => { - if (open) { - if (initialValues) { - setName(initialValues.name) - setDescription(initialValues.description) - setContent(initialValues.content) - } else { - setName('') - setDescription('') - setContent('') - } - setErrors({}) - } - }, [open, initialValues]) + const [prevOpen, setPrevOpen] = useState(false) + const [prevInitialValues, setPrevInitialValues] = useState(initialValues) + + if ((open && !prevOpen) || (open && initialValues !== prevInitialValues)) { + setName(initialValues?.name ?? '') + setDescription(initialValues?.description ?? '') + setContent(initialValues?.content ?? '') + setErrors({}) + } + if (open !== prevOpen) setPrevOpen(open) + if (initialValues !== prevInitialValues) setPrevInitialValues(initialValues) const hasChanges = useMemo(() => { if (!initialValues) return true diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/skills.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/skills.tsx index dadebcd9e0..f2aa01379e 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/skills.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/skills.tsx @@ -4,17 +4,8 @@ import { useState } from 'react' import { createLogger } from '@sim/logger' import { Plus, Search } from 'lucide-react' import { useParams } from 'next/navigation' -import { - Button, - Input, - Modal, - ModalBody, - ModalContent, - ModalFooter, - ModalHeader, -} from '@/components/emcn' -import { Skeleton } from '@/components/ui' -import { cn } from '@/lib/core/utils/cn' +import { Button, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader } from '@/components/emcn' +import { Input, Skeleton } from '@/components/ui' import { SkillModal } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/skills/components/skill-modal' import type { SkillDefinition } from '@/hooks/queries/skills' import { useDeleteSkill, useSkills } from '@/hooks/queries/skills' @@ -105,12 +96,7 @@ export function Skills() { <>
-
+
setSearchTerm(e.target.value)} disabled={isLoading} - className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-100' + className='h-auto flex-1 border-0 bg-transparent p-0 font-base leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' />
- {/* Invitation error - inline */} {invitationError && (

{invitationError instanceof Error && invitationError.message @@ -303,7 +256,6 @@ export function MemberInvitationCard({

)} - {/* Success message */} {inviteSuccess && (

Invitation sent successfully diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx index b78de6ed98..ecdb8251e0 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx @@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from 'react' import { createLogger } from '@sim/logger' +import type { TagItem } from '@/components/emcn' import { Skeleton } from '@/components/ui' import { useSession } from '@/lib/auth/auth-client' import { DEFAULT_TEAM_TIER_COST_LIMIT } from '@/lib/billing/constants' @@ -69,7 +70,7 @@ export function TeamManagement() { const [inviteSuccess, setInviteSuccess] = useState(false) - const [inviteEmail, setInviteEmail] = useState('') + const [inviteEmails, setInviteEmails] = useState([]) const [showWorkspaceInvite, setShowWorkspaceInvite] = useState(false) const [selectedWorkspaces, setSelectedWorkspaces] = useState< Array<{ workspaceId: string; permission: string }> @@ -129,7 +130,8 @@ export function TeamManagement() { }, [orgName, orgSlug, createOrgMutation]) const handleInviteMember = useCallback(async () => { - if (!session?.user || !activeOrganization?.id || !inviteEmail.trim()) return + const validEmails = inviteEmails.filter((e) => e.isValid).map((e) => e.value) + if (!session?.user || !activeOrganization?.id || validEmails.length === 0) return try { const workspaceInvitations = @@ -141,23 +143,21 @@ export function TeamManagement() { : undefined await inviteMutation.mutateAsync({ - email: inviteEmail.trim(), + emails: validEmails, orgId: activeOrganization.id, workspaceInvitations, }) - // Show success state setInviteSuccess(true) setTimeout(() => setInviteSuccess(false), 3000) - // Reset form - setInviteEmail('') + setInviteEmails([]) setSelectedWorkspaces([]) setShowWorkspaceInvite(false) } catch (error) { logger.error('Failed to invite member', error) } - }, [session?.user?.id, activeOrganization?.id, inviteEmail, selectedWorkspaces, inviteMutation]) + }, [session?.user?.id, activeOrganization?.id, inviteEmails, selectedWorkspaces, inviteMutation]) const handleWorkspaceToggle = useCallback((workspaceId: string, permission: string) => { setSelectedWorkspaces((prev) => { @@ -391,15 +391,15 @@ export function TeamManagement() { {adminOrOwner && !isInvitationsDisabled && (

{}} // No-op: data is auto-loaded by React Query + onLoadUserWorkspaces={async () => {}} onWorkspaceToggle={handleWorkspaceToggle} inviteSuccess={inviteSuccess} availableSeats={Math.max(0, totalSeats - usedSeats.used)} @@ -434,7 +434,7 @@ export function TeamManagement() { {/* Team Information */}
- + Team Information - + Billing Information orgId: string } @@ -264,12 +264,12 @@ export function useInviteMember() { const queryClient = useQueryClient() return useMutation({ - mutationFn: async ({ email, workspaceInvitations, orgId }: InviteMemberParams) => { + mutationFn: async ({ emails, workspaceInvitations, orgId }: InviteMemberParams) => { const response = await fetch(`/api/organizations/${orgId}/invitations?batch=true`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - emails: [email], + emails, workspaceInvitations, }), }) From e69cecaf16d15df480b9f853f007d3c6ee2db380 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 10 Feb 2026 15:09:39 -0800 Subject: [PATCH 4/7] updated placeholder --- apps/sim/blocks/blocks/confluence.ts | 4 ++-- apps/sim/blocks/blocks/jira.ts | 2 +- apps/sim/blocks/blocks/jira_service_management.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/sim/blocks/blocks/confluence.ts b/apps/sim/blocks/blocks/confluence.ts index 4c8790928e..970945c0c3 100644 --- a/apps/sim/blocks/blocks/confluence.ts +++ b/apps/sim/blocks/blocks/confluence.ts @@ -44,7 +44,7 @@ export const ConfluenceBlock: BlockConfig = { id: 'domain', title: 'Domain', type: 'short-input', - placeholder: 'Enter Confluence domain (e.g., simstudio.atlassian.net)', + placeholder: 'Enter Confluence domain (e.g., company.atlassian.net)', required: true, }, { @@ -462,7 +462,7 @@ export const ConfluenceV2Block: BlockConfig = { id: 'domain', title: 'Domain', type: 'short-input', - placeholder: 'Enter Confluence domain (e.g., simstudio.atlassian.net)', + placeholder: 'Enter Confluence domain (e.g., company.atlassian.net)', required: true, }, { diff --git a/apps/sim/blocks/blocks/jira.ts b/apps/sim/blocks/blocks/jira.ts index 60356a728f..63e06ea539 100644 --- a/apps/sim/blocks/blocks/jira.ts +++ b/apps/sim/blocks/blocks/jira.ts @@ -54,7 +54,7 @@ export const JiraBlock: BlockConfig = { title: 'Domain', type: 'short-input', required: true, - placeholder: 'Enter Jira domain (e.g., simstudio.atlassian.net)', + placeholder: 'Enter Jira domain (e.g., company.atlassian.net)', }, { id: 'credential', diff --git a/apps/sim/blocks/blocks/jira_service_management.ts b/apps/sim/blocks/blocks/jira_service_management.ts index 86ac86e759..11cda38587 100644 --- a/apps/sim/blocks/blocks/jira_service_management.ts +++ b/apps/sim/blocks/blocks/jira_service_management.ts @@ -49,7 +49,7 @@ export const JiraServiceManagementBlock: BlockConfig = { title: 'Domain', type: 'short-input', required: true, - placeholder: 'Enter Jira domain (e.g., simstudio.atlassian.net)', + placeholder: 'Enter Jira domain (e.g., company.atlassian.net)', }, { id: 'credential', From cba9caf390b8271fddb69f3edf0359a974ec2409 Mon Sep 17 00:00:00 2001 From: waleed Date: Tue, 10 Feb 2026 16:28:16 -0800 Subject: [PATCH 5/7] updated colors, error throwing in mcp modal --- .../deploy-modal/components/mcp/mcp.tsx | 19 +++++++++++++------ .../team-management/team-management.tsx | 4 ++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx index 6384fd75e8..5c82bb4721 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/components/mcp/mcp.tsx @@ -288,8 +288,9 @@ export function McpDeploy({ onSubmittingChange?.(true) setSaveErrors([]) try { - const nextServerToolsMap = { ...serverToolsMap } const errors: string[] = [] + const addedEntries: Record = {} + const removedIds: string[] = [] for (const serverId of toAdd) { setPendingServerChanges((prev) => new Set(prev).add(serverId)) @@ -302,7 +303,7 @@ export function McpDeploy({ toolDescription: toolDescription.trim() || undefined, parameterSchema, }) - nextServerToolsMap[serverId] = { tool: addedTool, isLoading: false } + addedEntries[serverId] = { tool: addedTool, isLoading: false } onAddedToServer?.() logger.info(`Added workflow ${workflowId} as tool to server ${serverId}`) } catch (error) { @@ -319,7 +320,7 @@ export function McpDeploy({ } for (const serverId of toRemove) { - const toolInfo = nextServerToolsMap[serverId] + const toolInfo = serverToolsMap[serverId] if (!toolInfo?.tool) continue setPendingServerChanges((prev) => new Set(prev).add(serverId)) @@ -329,7 +330,7 @@ export function McpDeploy({ serverId, toolId: toolInfo.tool.id, }) - delete nextServerToolsMap[serverId] + removedIds.push(serverId) } catch (error) { const serverName = servers.find((s) => s.id === serverId)?.name || serverId errors.push(`Failed to remove from ${serverName}`) @@ -346,7 +347,7 @@ export function McpDeploy({ if (shouldUpdateExisting) { for (const serverId of selectedServerIdsForForm) { if (toAdd.has(serverId)) continue - const toolInfo = nextServerToolsMap[serverId] + const toolInfo = serverToolsMap[serverId] if (!toolInfo?.tool) continue try { @@ -366,7 +367,13 @@ export function McpDeploy({ } } - setServerToolsMap(nextServerToolsMap) + setServerToolsMap((prev) => { + const next = { ...prev, ...addedEntries } + for (const id of removedIds) { + delete next[id] + } + return next + }) if (errors.length > 0) { setSaveErrors(errors) } else { diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx index ecdb8251e0..91448259f3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/team-management/team-management.tsx @@ -434,7 +434,7 @@ export function TeamManagement() { {/* Team Information */}
- +
Team Information - + Billing Information Date: Tue, 10 Feb 2026 16:48:23 -0800 Subject: [PATCH 6/7] ack comments --- .../components/settings-modal/components/mcp/mcp.tsx | 11 +++++++++++ apps/sim/lib/mcp/workflow-mcp-sync.ts | 6 +----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx index bd8a01c9c5..b8a277acc3 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx @@ -421,6 +421,7 @@ export function MCP({ initialServerId }: MCPProps) { const [editFormData, setEditFormData] = useState(DEFAULT_FORM_DATA) const [editOriginalData, setEditOriginalData] = useState(DEFAULT_FORM_DATA) const [isUpdatingServer, setIsUpdatingServer] = useState(false) + const [editSaveError, setEditSaveError] = useState(null) const [editShowEnvVars, setEditShowEnvVars] = useState(false) const [editEnvSearchTerm, setEditEnvSearchTerm] = useState('') const [editCursorPosition, setEditCursorPosition] = useState(0) @@ -808,6 +809,7 @@ export function MCP({ initialServerId }: MCPProps) { setEditFormData(data) setEditOriginalData(JSON.parse(JSON.stringify(data))) setShowEditModal(true) + setEditSaveError(null) clearEditTestResult() resetEditEnvVarState() setEditUrlScrollLeft(0) @@ -823,6 +825,7 @@ export function MCP({ initialServerId }: MCPProps) { setShowEditModal(false) setEditFormData(DEFAULT_FORM_DATA) setEditOriginalData(DEFAULT_FORM_DATA) + setEditSaveError(null) clearEditTestResult() resetEditEnvVarState() }, [clearEditTestResult, resetEditEnvVarState]) @@ -934,6 +937,7 @@ export function MCP({ initialServerId }: MCPProps) { if (!selectedServerId || !editFormData.name.trim()) return setIsUpdatingServer(true) + setEditSaveError(null) try { const headersRecord = headersToRecord(editFormData.headers) const serverConfig = { @@ -969,6 +973,8 @@ export function MCP({ initialServerId }: MCPProps) { setShowEditModal(false) logger.info(`Updated MCP server: ${editFormData.name}`) } catch (error) { + const message = error instanceof Error ? error.message : 'Failed to update server' + setEditSaveError(message) logger.error('Failed to update MCP server:', error) } finally { setIsUpdatingServer(false) @@ -1336,6 +1342,11 @@ export function MCP({ initialServerId }: MCPProps) {
+ {editSaveError && ( +

+ {editSaveError} +

+ )}