{"record":{"id":"9aad2bc8a65be829","repo":"siyuan-note/siyuan","slug":"mcp-oauth-authorization-required","errorCode":null,"errorMessage":"mcp oauth authorization required","messagePattern":"mcp oauth authorization required","errorType":"http","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"kernel/mcp/client/oauth.go","lineNumber":47,"sourceCode":"\t\"net/url\"\n\t\"slices\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/modelcontextprotocol/go-sdk/auth\"\n\t\"github.com/modelcontextprotocol/go-sdk/oauthex\"\n\t\"github.com/siyuan-note/httpclient\"\n\t\"github.com/siyuan-note/logging\"\n\t\"github.com/siyuan-note/siyuan/kernel/conf\"\n\t\"github.com/siyuan-note/siyuan/kernel/util\"\n\t\"golang.org/x/oauth2\"\n)\n\nconst oauthAuthorizationTimeout = 5 * time.Minute\n\nvar errOAuthAuthorizationRequired = errors.New(\"mcp oauth authorization required\")\n\ntype oauthCallbackResult struct {\n\tCode  string\n\tState string\n\tError string\n}\n\ntype oauthFlow struct {\n\tState   string\n\tIssuer  string\n\tResult  chan oauthCallbackResult\n\tExpires time.Time\n}\n\nvar oauthFlows = struct {\n\tsync.Mutex\n\titems map[string]*oauthFlow\n}{items: map[string]*oauthFlow{}}","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/oauth.go#L29-L65","documentation":"Sentinel error errOAuthAuthorizationRequired returned by mcpOAuthHandler.Authorize when the server returned a Bearer challenge, no usable stored credential exists (or refresh failed permanently and was cleared), and the current flow is non-interactive (e.g. background reconnect). It signals that a human-driven browser authorization is required and cannot be completed now.","triggerScenarios":"connectHTTP runs non-interactively (interactive == false), the MCP server responds 401 with an OAuth Bearer challenge, and either there is no stored credential, the stored credential's refresh failed permanently (credential was cleared), or the credential was rejected. Authorize sets runtime state 'authorization_required' and returns this sentinel.","commonSituations":"First time adding an OAuth-protected MCP server (no cached token) while the kernel reconnects in the background; refresh token expired/revoked and the user has not re-authorized; the credential was marked Rejected by the auth server.","solutions":["Trigger an interactive connect from the UI so the OAuth handler can open the browser flow and capture the callback; the connect timeout automatically extends by oauthAuthorizationTimeout when interactive.","Complete the browser authorization once; the resulting credential is stored and subsequent non-interactive reconnects will use refresh.","If authorization keeps being required, check that the stored credential's Issuer matches the auth server's Issuer (mismatched issuers prevent refresh).","Confirm the auth server supports PKCE S256 and the authorization_code grant, otherwise the interactive flow will also fail later in Authorize."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Determine whether OAuth will be required before non-interactive connect.\nfunc needsOAuth(server conf.MCPServer) bool {\n    return server.Type == \"http\" && !hasAuthorizationHeader(server.Headers)\n}","typeGuard":null,"tryCatchPattern":"// Compare with errors.Is against the sentinel.\nif errors.Is(err, client.ErrOAuthAuthorizationRequired) { // (if exported)\n    // trigger interactive connect from UI\n}","preventionTips":["Run the first connect interactively so the OAuth flow can complete and cache a token.","For background reconnects, expect this sentinel and surface a 're-authorize' prompt to the user."],"tags":["mcp","oauth","auth","interactive"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}