From f2686e9e390b66df6694f27eba24114d30ae7194 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Mon, 29 Jun 2026 20:48:59 +0800 Subject: [PATCH] feat(mcp): drive auth by AuthType and auto-detect OAuth on test Select the auth header for the configured strategy only, instead of emitting both X-API-Key and Authorization whenever the fields happened to be set (which double-authed after switching strategies). AuthType="" keeps the legacy infer-from-fields behavior for pre-existing rows. Add a configurable api_key_header (non-secret, defaults to X-API-Key) so services expecting the key in a different header work without plaintext custom headers. On connection test, upgrade a bare 401 that advertises RFC 9728 protected-resource metadata into an explicit OAuthRequired signal so the UI can guide the user to switch to OAuth 2.0. Surface the server's description from the initialize handshake in the test result. --- client/mcp_service.go | 9 +- docs/api/mcp-service.md | 1 + frontend/src/api/mcp-service.ts | 9 +- frontend/src/i18n/locales/en-US.ts | 11 +- frontend/src/i18n/locales/ko-KR.ts | 9 + frontend/src/i18n/locales/ru-RU.ts | 11 +- frontend/src/i18n/locales/zh-CN.ts | 8 +- .../settings/components/McpServiceDialog.vue | 274 ++++++++++++------ .../settings/components/McpTestResultBody.vue | 27 ++ internal/application/service/mcp_service.go | 48 ++- internal/handler/dto/mcp.go | 32 +- internal/handler/mcp_service.go | 5 + internal/mcp/client.go | 102 +++++-- internal/mcp/client_test.go | 123 ++++++++ internal/mcp/types.go | 6 +- internal/types/mcp.go | 26 +- 16 files changed, 546 insertions(+), 155 deletions(-) create mode 100644 internal/mcp/client_test.go diff --git a/client/mcp_service.go b/client/mcp_service.go index abc18600..f5680c04 100644 --- a/client/mcp_service.go +++ b/client/mcp_service.go @@ -78,10 +78,11 @@ type MCPResource struct { // MCPTestResult represents the result of testing an MCP service connection type MCPTestResult struct { - Success bool `json:"success"` - Message string `json:"message,omitempty"` - Tools []*MCPTool `json:"tools,omitempty"` - Resources []*MCPResource `json:"resources,omitempty"` + Success bool `json:"success"` + Message string `json:"message,omitempty"` + Description string `json:"description,omitempty"` + Tools []*MCPTool `json:"tools,omitempty"` + Resources []*MCPResource `json:"resources,omitempty"` } // CreateMCPService creates a new MCP service diff --git a/docs/api/mcp-service.md b/docs/api/mcp-service.md index 46b539bf..6102ab88 100644 --- a/docs/api/mcp-service.md +++ b/docs/api/mcp-service.md @@ -306,6 +306,7 @@ curl --location --request POST 'http://localhost:8080/api/v1/mcp-services/mcp-00 "data": { "success": true, "message": "连接成功", + "description": "提供全球天气信息查询", "tools": [ { "name": "get_weather", diff --git a/frontend/src/api/mcp-service.ts b/frontend/src/api/mcp-service.ts index 4404690f..033d25f9 100644 --- a/frontend/src/api/mcp-service.ts +++ b/frontend/src/api/mcp-service.ts @@ -19,6 +19,10 @@ export interface MCPService { // optional-property typing remains so create-mode payloads can still // carry them in the initial POST body. api_key?: string + // Header name carrying api_key when auth_type is "api_key". Non-secret; + // empty defaults to "X-API-Key". Lets services expecting the key in a + // different header (e.g. raw token in "Authorization") work. + api_key_header?: string token?: string custom_headers?: Record // OAuth-only, non-secret configuration. @@ -69,6 +73,10 @@ export interface MCPResource { export interface MCPTestResult { success: boolean message?: string + description?: string + // Set when the server requires OAuth (RFC 9728) but the service was not + // configured for it — the UI guides the user to switch to OAuth 2.0. + oauth_required?: boolean tools?: MCPTool[] resources?: MCPResource[] } @@ -212,4 +220,3 @@ export async function resolveToolApproval( ): Promise { await post(`/api/v1/agent/tool-approvals/${encodeURIComponent(pendingId)}`, body) } - diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 726e2ec0..b53517cf 100755 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -4465,6 +4465,14 @@ export default { addEnvVar: 'Add Environment Variable', enableService: 'Enable Service', authConfig: 'Authentication', + authType: 'Auth Method', + authTypeNone: 'None / Custom Header', + authTypeApiKey: 'API Key / Token', + authTypeOAuth: 'OAuth 2.0 (authorize on first connect)', + oauthAuthorizeHint: 'Clicking "Authorize" saves the current config first, then starts authorization (each user authorizes individually).', + apiKeyHeader: 'Header Name', + apiKeyHeaderDesc: 'Defaults to X-API-Key. For Bearer, set Authorization and put "Bearer " in the value below; for raw-token services use Authorization with the raw token.', + credentialValue: 'Secret / Token', apiKey: 'API Key', bearerToken: 'Bearer Token', optional: 'Optional', @@ -4484,7 +4492,8 @@ export default { created: 'MCP service created', updated: 'MCP service updated', createFailed: 'Failed to create MCP service', - updateFailed: 'Failed to update MCP service' + updateFailed: 'Failed to update MCP service', + oauthRequired: 'This server requires OAuth. Switched to OAuth 2.0 — save, then click "Authorize".' }, customHeaders: { label: 'Custom Headers (optional)', diff --git a/frontend/src/i18n/locales/ko-KR.ts b/frontend/src/i18n/locales/ko-KR.ts index ca21ce75..b2d2fc4d 100755 --- a/frontend/src/i18n/locales/ko-KR.ts +++ b/frontend/src/i18n/locales/ko-KR.ts @@ -4461,6 +4461,14 @@ export default { addEnvVar: "환경 변수 추가", enableService: "서비스 활성화", authConfig: "인증 설정", + authType: "인증 방식", + authTypeNone: "없음 / 사용자 정의 헤더", + authTypeApiKey: "API Key / Token", + authTypeOAuth: "OAuth 2.0(최초 연결 시 인증)", + oauthAuthorizeHint: "「인증하기」를 클릭하면 현재 설정을 먼저 자동 저장한 후 인증을 시작합니다(사용자별 개별 인증).", + apiKeyHeader: "요청 헤더 이름", + apiKeyHeaderDesc: "비워 두면 기본값은 X-API-Key입니다. Bearer 방식은 Authorization을 입력하고 아래 비밀 값에 \"Bearer \"을 작성하세요. 원시 토큰이 필요하면 Authorization에 토큰을 그대로 입력합니다.", + credentialValue: "비밀 값 / 토큰", apiKey: "API Key", bearerToken: "Bearer Token", optional: "선택", @@ -4481,6 +4489,7 @@ export default { updated: "MCP 서비스가 업데이트되었습니다", createFailed: "MCP 서비스 생성 실패", updateFailed: "MCP 서비스 업데이트 실패", + oauthRequired: "이 서비스는 OAuth 인증이 필요하여 OAuth 2.0으로 자동 전환했습니다. 저장 후 「인증하기」를 클릭하세요.", }, customHeaders: { label: "사용자 지정 헤더(선택 사항)", diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 221e13df..fbf71a23 100755 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -4113,6 +4113,14 @@ export default { addEnvVar: 'Добавить переменную', enableService: 'Включить сервис', authConfig: 'Аутентификация', + authType: 'Способ аутентификации', + authTypeNone: 'Нет / Свой заголовок', + authTypeApiKey: 'API Key / Token', + authTypeOAuth: 'OAuth 2.0 (авторизация при первом подключении)', + oauthAuthorizeHint: 'Нажатие «Авторизоваться» сначала сохранит текущую конфигурацию, затем запустит авторизацию (каждый пользователь авторизуется отдельно).', + apiKeyHeader: 'Имя заголовка', + apiKeyHeaderDesc: 'По умолчанию X-API-Key. Для Bearer укажите Authorization и впишите "Bearer " в значение ниже; для «сырого» токена используйте Authorization и сам токен.', + credentialValue: 'Секрет / Токен', apiKey: 'API Key', bearerToken: 'Bearer Token', optional: 'Необязательно', @@ -4132,7 +4140,8 @@ export default { created: 'Сервис MCP создан', updated: 'Сервис MCP обновлён', createFailed: 'Не удалось создать сервис MCP', - updateFailed: 'Не удалось обновить сервис MCP' + updateFailed: 'Не удалось обновить сервис MCP', + oauthRequired: 'Сервис требует OAuth. Переключено на OAuth 2.0 — сохраните и нажмите «Авторизоваться».' }, customHeaders: { label: 'Пользовательские заголовки (необязательно)', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 70eb059b..2cba47a1 100755 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -4468,9 +4468,11 @@ export default { authConfig: "认证配置", authType: "认证方式", authTypeNone: "无 / 自定义 Header", - authTypeApiKey: "API Key", - authTypeBearer: "Bearer Token", + authTypeApiKey: "API Key / Token", authTypeOAuth: "OAuth 2.0(首次连接授权)", + apiKeyHeader: "请求头名称", + apiKeyHeaderDesc: "留空默认 X-API-Key。Bearer 方式请填 Authorization,并在下方密钥值中写 “Bearer ”;需要裸 token 时填 Authorization 并直接填入 token。", + credentialValue: "密钥值 / Token", oauthScopes: "Scopes(可选,空格分隔)", oauthAuthorization: "授权状态", oauthAuthorized: "已授权", @@ -4479,6 +4481,7 @@ export default { oauthReauthorize: "重新授权", oauthRevoke: "撤销授权", oauthSaveFirstHint: "保存服务后,可在编辑页发起首次授权(每个用户独立授权)。", + oauthAuthorizeHint: "点击「去授权」会先自动保存当前配置,再发起授权(每个用户独立授权)。", apiKey: "API Key", bearerToken: "Bearer Token", optional: "可选", @@ -4499,6 +4502,7 @@ export default { updated: "MCP 服务已更新", createFailed: "创建 MCP 服务失败", updateFailed: "更新 MCP 服务失败", + oauthRequired: "该服务需要 OAuth 授权,已自动切换为 OAuth 2.0,请保存后点击「去授权」。", authorized: "授权成功", authorizeFailed: "发起授权失败", revoked: "已撤销授权", diff --git a/frontend/src/views/settings/components/McpServiceDialog.vue b/frontend/src/views/settings/components/McpServiceDialog.vue index e280da21..b0d98c82 100644 --- a/frontend/src/views/settings/components/McpServiceDialog.vue +++ b/frontend/src/views/settings/components/McpServiceDialog.vue @@ -196,7 +196,19 @@
- + +
+ +
@@ -205,7 +217,7 @@ -
+
@@ -217,13 +229,13 @@ {{ oauthAuthorized ? t('mcpServiceDialog.oauthReauthorize', '重新授权') : t('mcpServiceDialog.oauthAuthorize', '去授权') }}
+

+ {{ t('mcpServiceDialog.oauthAuthorizeHint', '点击「去授权」会先自动保存当前配置,再发起授权(每个用户独立授权)。') }} +

-

- {{ t('mcpServiceDialog.oauthSaveFirstHint', '保存服务后,可在编辑页发起首次授权(每个用户独立授权)。') }} -

-