Skip to content

Commit 8cdadd2

Browse files
committed
fix(mcp): address oauth review notes
1 parent e398368 commit 8cdadd2

37 files changed

Lines changed: 611 additions & 285 deletions

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Plan
2+
3+
## Implementation
4+
5+
1. Patch the smallest root locations:
6+
- `mcpOAuthManager.ts` for OAuth error shape checks and stale-flow guard.
7+
- `mcpOAuthProvider.ts` for authorization URL scheme validation.
8+
- `oauthCredentialStore.ts` for scoped deletion persistence.
9+
- renderer OAuth callback handlers for in-flight guards.
10+
- `McpServerCard.vue` for the auth-error label.
11+
- shared contracts for stricter callback/status validation.
12+
2. Replace the source-text sheet test with a mounted DOM assertion.
13+
3. Update the new OpenAI Codex/MCP auth strings in affected locale JSON files.
14+
15+
## Test Strategy
16+
17+
- Extend MCP OAuth manager tests for HTTP status classification.
18+
- Add a credential store scoped-clear test.
19+
- Update the SheetContent drag test to mount the component.
20+
- Run focused tests for MCP OAuth/store/renderer components plus format, i18n, lint, and typecheck.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# PR 1870 Review Fixes
2+
3+
## User Need
4+
5+
PR review comments on MCP and Codex OAuth should be verified against the current code and fixed
6+
where they point to real bugs or user-visible polish issues.
7+
8+
## Goal
9+
10+
Address valid review feedback for OAuth error classification, stale OAuth flow handling, callback
11+
submission guards, URL validation, credential clearing, status labels, rendered drag-region tests,
12+
and newly added locale strings.
13+
14+
## Acceptance Criteria
15+
16+
- MCP OAuth classifies HTTP 401 errors from common error shapes.
17+
- Superseded MCP OAuth flows cannot complete and overwrite the active flow.
18+
- Invalid pasted MCP callback URLs return the error status created by the failure path.
19+
- MCP OAuth authorization opens only `http:` or `https:` URLs.
20+
- Scoped MCP OAuth credential clearing actually removes the targeted scope.
21+
- OpenAI Codex and MCP callback submissions cannot double-submit while already in flight.
22+
- MCP auth error cards show the failure label, not the required-auth label.
23+
- The Sheet no-drag regression test checks rendered DOM.
24+
- Newly added auth strings are localized in the affected locale files.
25+
26+
## Non-Goals
27+
28+
- Do not redesign OAuth.
29+
- Do not add a new permission model.
30+
- Do not translate unrelated legacy strings.
31+
32+
## Open Questions
33+
34+
None.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Tasks
2+
3+
- [x] Fetch and verify PR review comments.
4+
- [x] Patch OAuth manager/provider/store issues.
5+
- [x] Patch renderer duplicate-submit and status-label issues.
6+
- [x] Tighten route/event schemas.
7+
- [x] Replace sheet source-text test with DOM assertion.
8+
- [x] Localize new auth strings in affected locales.
9+
- [x] Run focused tests and required validation commands.

src/main/presenter/mcpPresenter/mcpOAuthManager.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,21 @@ function isOAuthError(error: unknown): boolean {
6666
return true
6767
}
6868

69-
const status = (error as { code?: unknown; status?: unknown; httpStatus?: unknown } | undefined)
70-
?.code
71-
if (status === 401) {
69+
const errorLike = error as
70+
| {
71+
code?: unknown
72+
status?: unknown
73+
httpStatus?: unknown
74+
response?: { status?: unknown }
75+
}
76+
| undefined
77+
const statuses = [
78+
errorLike?.code,
79+
errorLike?.status,
80+
errorLike?.httpStatus,
81+
errorLike?.response?.status
82+
]
83+
if (statuses.some((status) => status === 401 || status === '401')) {
7284
return true
7385
}
7486

@@ -261,14 +273,15 @@ export class McpOAuthManager {
261273

262274
const resolution = flow.callbackSession.resolveCallbackUrl(callbackUrl)
263275
if (resolution.kind === 'not-found') {
264-
this.setStatus({
276+
const status: McpServerAuthStatus = {
265277
serverName,
266278
state: 'error',
267279
authenticated: false,
268280
error: 'MCP OAuth callback URL is invalid',
269281
storage: this.store.getStorageState()
270-
})
271-
return this.getStatus(serverName, config)
282+
}
283+
this.setStatus(status)
284+
return status
272285
}
273286

274287
await flow.flowPromise
@@ -302,6 +315,10 @@ export class McpOAuthManager {
302315
}
303316

304317
private finishAuthenticatedFlow(flow: PendingMcpOAuthFlow): void {
318+
if (this.pendingFlows.get(flow.serverName) !== flow) {
319+
return
320+
}
321+
305322
this.pendingFlows.delete(flow.serverName)
306323
flow.callbackSession.close()
307324
const entry = this.store.load(flow.credentialKey)

src/main/presenter/mcpPresenter/mcpOAuthProvider.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export class DeepChatMcpOAuthProvider implements OAuthClientProvider {
6969
if (!this.interactive) {
7070
throw new Error('MCP OAuth authentication is required')
7171
}
72+
if (authorizationUrl.protocol !== 'http:' && authorizationUrl.protocol !== 'https:') {
73+
throw new Error('MCP OAuth authorization URL must use http or https')
74+
}
7275

7376
await shell.openExternal(authorizationUrl.toString())
7477
}

src/main/presenter/mcpPresenter/oauthCredentialStore.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ export class McpOAuthCredentialStore {
8282
return
8383
}
8484

85-
const current = this.load(key)
85+
const entries = this.loadAll()
86+
const current = entries[key]
8687
if (!current) {
8788
return
8889
}
@@ -98,7 +99,8 @@ export class McpOAuthCredentialStore {
9899
delete next.discoveryState
99100
}
100101

101-
this.saveEntry(key, next)
102+
entries[key] = next
103+
this.saveAll(entries)
102104
}
103105

104106
private loadAll(): McpOAuthCredentialData {

src/renderer/settings/components/OpenAICodexOAuth.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ const startBrowserLogin = () =>
285285
runAuthAction('browser', () => oauthClient.startOpenAICodexBrowserLogin())
286286
287287
const completeBrowserLoginFromUrl = () => {
288+
if (busyAction.value === 'callback') {
289+
return
290+
}
291+
288292
const url = callbackUrl.value.trim()
289293
if (!url) {
290294
return

src/renderer/src/components/mcp-config/components/McpServerCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const statusConfig = computed(() => {
116116
case 'auth-error':
117117
return {
118118
dot: 'bg-red-500',
119-
text: t('settings.mcp.authRequired'),
119+
text: t('settings.mcp.authFailed'),
120120
color: 'text-red-600 dark:text-red-400'
121121
}
122122
case 'error':

src/renderer/src/components/mcp-config/components/McpServers.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,10 @@ const handleAuthenticateServer = async (serverName: string) => {
248248
}
249249
250250
const submitAuthCallbackUrl = async () => {
251+
if (isSubmittingAuthCallback.value) {
252+
return
253+
}
254+
251255
const serverName = selectedServerForAuth.value
252256
const callbackUrl = authCallbackUrl.value.trim()
253257
if (!serverName || !callbackUrl) {

src/renderer/src/i18n/da-DK/settings.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,11 +1064,11 @@
10641064
"advanced": "Advanced"
10651065
}
10661066
},
1067-
"openaiCodexPasteCallback": "Paste callback URL",
1068-
"openaiCodexCallbackTitle": "Complete authentication",
1069-
"openaiCodexCallbackDescription": "After the browser finishes, return to DeepChat. If DeepChat does not update, paste the full callback URL here.",
1070-
"openaiCodexCallbackPlaceholder": "Paste the full callback URL",
1071-
"openaiCodexCompleteAuthentication": "Complete authentication"
1067+
"openaiCodexPasteCallback": "Indsæt callback-URL",
1068+
"openaiCodexCallbackTitle": "Fuldfør godkendelse",
1069+
"openaiCodexCallbackDescription": "Når browseren er færdig, skal du vende tilbage til DeepChat. Hvis DeepChat ikke opdateres, skal du indsætte den fulde callback-URL her.",
1070+
"openaiCodexCallbackPlaceholder": "Indsæt den fulde callback-URL",
1071+
"openaiCodexCompleteAuthentication": "Fuldfør godkendelse"
10721072
},
10731073
"knowledgeBase": {
10741074
"title": "Indstillinger for vidensbase",
@@ -1387,15 +1387,15 @@
13871387
"custom": "Custom"
13881388
}
13891389
},
1390-
"authRequired": "Authentication required",
1391-
"authenticate": "Authenticate",
1392-
"authFailed": "Authentication failed",
1393-
"authCallbackTitle": "Complete authentication",
1394-
"authCallbackDescription": "After the browser finishes, return to DeepChat. If DeepChat does not update, paste the full callback URL here.",
1395-
"authCallbackPlaceholder": "Paste the full callback URL",
1396-
"completeAuthentication": "Complete authentication",
1397-
"saveSuccess": "Configuration saved",
1398-
"saveFailed": "Failed to save configuration"
1390+
"authRequired": "Godkendelse påkrævet",
1391+
"authenticate": "Godkend",
1392+
"authFailed": "Godkendelse mislykkedes",
1393+
"authCallbackTitle": "Fuldfør godkendelse",
1394+
"authCallbackDescription": "Når browseren er færdig, skal du vende tilbage til DeepChat. Hvis DeepChat ikke opdateres, skal du indsætte den fulde callback-URL her.",
1395+
"authCallbackPlaceholder": "Indsæt den fulde callback-URL",
1396+
"completeAuthentication": "Fuldfør godkendelse",
1397+
"saveSuccess": "Konfiguration gemt",
1398+
"saveFailed": "Kunne ikke gemme konfiguration"
13991399
},
14001400
"scheduledTasks": {
14011401
"title": "Planlagte opgaver",

0 commit comments

Comments
 (0)