Compare commits

..

2 Commits

Author SHA1 Message Date
a4ccf5680d fix(web): sort new i18n keys to satisfy jsonc/sort-keys
Move the M3 forwardUserIdentity / forwardUserIdentityTip entries to their
alphabetical position (between editTitle and headerKey) so the lint rule
`jsonc/sort-keys` passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 20:02:33 -07:00
029b836234 feat(web): add Forward-user-identity toggle to MCP provider modal (M3)
Exposes the M2 backend flags as a switch on the MCP provider create/edit
modal so workspace admins can opt in to enterprise SSO identity-forwarding
per provider. When the toggle flips on, the modal sends
forward_user_identity=true + identity_mode="idp_token" to the console API
(which the M2 backend persists on tool_mcp_providers).

- The toggle lives between Server Identifier and the Authentication tabs;
  it overrides the static Authorization (from Auth/Headers) at invoke time.
- The form-state hook hydrates from the GET response so editing preserves
  the previous choice across sessions.
- en-US + zh-Hans strings added; other locales fall back to en-US.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-29 19:53:36 -07:00
8 changed files with 42 additions and 8 deletions

0
.codex Normal file
View File

View File

@ -1,8 +0,0 @@
# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
version = 1
name = "dify"
[setup]
script = '''
pnpm install --frozen-lockfile --prefer-offline
'''

View File

@ -54,6 +54,7 @@ type MCPModalFormState = {
isDynamicRegistration: boolean
clientID: string
credentials: string
forwardUserIdentity: boolean
}
type MCPModalFormActions = {
setUrl: (url: string) => void
@ -68,6 +69,7 @@ type MCPModalFormActions = {
setIsDynamicRegistration: (value: boolean) => void
setClientID: (id: string) => void
setCredentials: (credentials: string) => void
setForwardUserIdentity: (value: boolean) => void
handleUrlBlur: (url: string) => Promise<void>
resetIcon: () => void
}
@ -100,6 +102,11 @@ export const useMCPModalForm = (data?: ToolWithProvider) => {
const [isDynamicRegistration, setIsDynamicRegistration] = useState(() => isCreate ? true : (data?.is_dynamic_registration ?? true))
const [clientID, setClientID] = useState(() => data?.authentication?.client_id || '')
const [credentials, setCredentials] = useState(() => data?.authentication?.client_secret || '')
// M3 — user-identity forwarding. Identity mode is implied by the toggle:
// off → "off", on → "idp_token" (only mode currently supported).
const [forwardUserIdentity, setForwardUserIdentity] = useState(
() => Boolean(data?.forward_user_identity),
)
const handleUrlBlur = useCallback(async (urlValue: string) => {
if (data)
return
@ -163,6 +170,7 @@ export const useMCPModalForm = (data?: ToolWithProvider) => {
isDynamicRegistration,
clientID,
credentials,
forwardUserIdentity,
} satisfies MCPModalFormState,
// Actions
actions: {
@ -178,6 +186,7 @@ export const useMCPModalForm = (data?: ToolWithProvider) => {
setIsDynamicRegistration,
setClientID,
setCredentials,
setForwardUserIdentity,
handleUrlBlur,
resetIcon,
} satisfies MCPModalFormActions,

View File

@ -5,6 +5,7 @@ import type { ToolWithProvider } from '@/app/components/workflow/types'
import type { AppIconType } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
import { Switch } from '@langgenius/dify-ui/switch'
import { toast } from '@langgenius/dify-ui/toast'
import { RiCloseLine, RiEditLine } from '@remixicon/react'
import { useHover } from 'ahooks'
@ -39,6 +40,8 @@ type MCPModalConfirmPayload = {
timeout: number
sse_read_timeout: number
}
forward_user_identity?: boolean
identity_mode?: 'off' | 'idp_token'
}
type DuplicateAppModalProps = {
@ -110,6 +113,8 @@ const MCPModalContent: FC<MCPModalContentProps> = ({
timeout: state.timeout || 30,
sse_read_timeout: state.sseReadTimeout || 300,
},
forward_user_identity: state.forwardUserIdentity,
identity_mode: state.forwardUserIdentity ? 'idp_token' : 'off',
})
if (isCreate)
onHide()
@ -207,6 +212,23 @@ const MCPModalContent: FC<MCPModalContentProps> = ({
)}
</div>
{/* Forward user identity (M3 — enterprise SSO identity-forwarding) */}
<div>
<div className="mb-1 flex h-6 items-center">
<Switch
className="mr-2"
checked={state.forwardUserIdentity}
onCheckedChange={actions.setForwardUserIdentity}
/>
<span className="system-sm-medium text-text-secondary">
{t('mcp.modal.forwardUserIdentity', { ns: 'tools' })}
</span>
</div>
<div className="body-xs-regular text-text-tertiary">
{t('mcp.modal.forwardUserIdentityTip', { ns: 'tools' })}
</div>
</div>
{/* Auth Method Tabs */}
<TabSlider
className="w-full"

View File

@ -78,6 +78,9 @@ export type Collection = {
timeout?: number
sse_read_timeout?: number
}
// M3 — user-identity forwarding (MCP)
forward_user_identity?: boolean
identity_mode?: 'off' | 'idp_token'
// Workflow
workflow_app_id?: string
}

View File

@ -120,6 +120,8 @@
"mcp.modal.configurations": "Configurations",
"mcp.modal.confirm": "Add & Authorize",
"mcp.modal.editTitle": "Edit MCP Server (HTTP)",
"mcp.modal.forwardUserIdentity": "Forward user identity",
"mcp.modal.forwardUserIdentityTip": "Send the calling user's verified SSO identity to this MCP server as an Authorization Bearer token. Requires Dify Enterprise SSO.",
"mcp.modal.headerKey": "Header Name",
"mcp.modal.headerKeyPlaceholder": "e.g., Authorization",
"mcp.modal.headerValue": "Header Value",

View File

@ -120,6 +120,8 @@
"mcp.modal.configurations": "配置",
"mcp.modal.confirm": "添加并授权",
"mcp.modal.editTitle": "修改 MCP 服务 (HTTP)",
"mcp.modal.forwardUserIdentity": "转发用户身份",
"mcp.modal.forwardUserIdentityTip": "将调用用户的已验证 SSO 身份作为 Authorization Bearer token 转发到该 MCP 服务器。需要 Dify Enterprise SSO。",
"mcp.modal.headerKey": "请求头名称",
"mcp.modal.headerKeyPlaceholder": "例如Authorization",
"mcp.modal.headerValue": "请求头值",

View File

@ -106,6 +106,8 @@ export const useCreateMCP = () => {
timeout?: number
sse_read_timeout?: number
headers?: Record<string, string>
forward_user_identity?: boolean
identity_mode?: 'off' | 'idp_token'
}) => {
return post<ToolWithProvider>('workspaces/current/tool-provider/mcp', {
body: {
@ -133,6 +135,8 @@ export const useUpdateMCP = ({
timeout?: number
sse_read_timeout?: number
headers?: Record<string, string>
forward_user_identity?: boolean
identity_mode?: 'off' | 'idp_token'
}) => {
return put('workspaces/current/tool-provider/mcp', {
body: {