You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every time agy CLI is restarted in WSL, the token storage file ~/.gemini/antigravity-cli/mcp_oauth_tokens.json is cleared to {}. This forces the user to re-authenticate with Notion and other MCP Servers on every single launch.
Root Cause
When the MCP manager attempts to connect using the stored token and receives a 400 Bad Request from the Notion server during the notifications/roots/list_changed message phase, connector.go treats ANY error as token invalidation and calls tokenStore.removeToken(serverURL).
Complete Analysis Report
Please see the pasted bug analysis below for the detailed call stack, timeline logs, and recommended fix.
Bug Analysis Report: agy Clears MCP OAuth Token File on Every Restart
Caution
This bug has been confirmed across 8 separate session logs. Every time agy CLI is launched (with a Notion MCP server configured), the local OAuth credentials file is completely cleared.
Issue Overview
Whenever agy CLI restarts in WSL (Windows Subsystem for Linux), the token storage file ~/.gemini/antigravity-cli/mcp_oauth_tokens.json is cleared to {}. This results in the loss of the OAuth token for the Notion MCP Server, forcing the user to re-authenticate on every launch.
Key Evidence
1. Token File Status
File
Size
Modification Time
Content
mcp_oauth_tokens.json
2 bytes
21:00:09 (After new session startup)
{} (Cleared)
mcp_oauth_tokens.json.back
457 bytes
20:59:55 (At the end of previous session)
Contains valid Notion OAuth Token
2. Reproduction History (All Confirmed Sessions)
Session Time
Error Message
Result
2026-06-08 21:58
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-08 22:10
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-09 19:39
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-09 20:30
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-09 20:40
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-09 20:50
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-09 20:53
Connection with stored token failed for notion: ... Bad Request
Token cleared
2026-06-09 21:00
Connection with stored token failed for notion: ... Bad Request
Token cleared
100% Reproduction Rate: All agy sessions configured with the Notion MCP server trigger this bug on startup.
3. Precise Timeline (Extracted from Logs)
21:00:07.978891 agy starts, initializing servers
21:00:07.979817 token_storage.go:55] Using file-based token storage because WSL environment detected
21:00:08.600409 auth.go:114] ChainedAuth: authenticated via keyring (effective: keyring)
21:00:08.600449 server_oauth.go] OAuth: authenticated successfully as xendomn@gmail.com
--- Token file modified (cleared) at 21:00:09.206907 ---
21:00:09.207472 connector.go:190] Connection with stored token failed for notion:
calling "initialize": sending "notifications/roots/list_changed": Bad Request
Important
The modification timestamp of the token file (21:00:09.206) is almost identical to the timestamp of the warning log in connector.go:190 (21:00:09.207). This 1ms interval demonstrates that the connection failure immediately triggers the token deletion logic in connector.go.
Two Distinct MCP Failure Modes
Analyzing the logs reveals two completely different MCP failure patterns:
Pattern
HTTP Status
Trigger Phase
Behavior
Startup Connection
400 Bad Request
Sending notifications/roots/list_changed
Clears local Token (BUG)
Active Session
401 Unauthorized
Calling tools/call or initialize
Triggers Re-authentication (Normal)
While 401 Unauthorized is the correct indicator of token expiration, 400 Bad Request (which is typically a protocol or server-side error) is being incorrectly treated as a token invalidation trigger.
Root Cause: Treating "Bad Request" as Token Invalidation
Code Call Hierarchy
Reconstructing the Go package symbols from the binary PCLN tables reveals the following call stack:
mcp.(*McpManager).startSession()
└── connector.go: Attempt to connect using the stored token
└── Send MCP initialization handshake ("notifications/roots/list_changed")
└── Notion Server responds: 400 Bad Request
└── connector.go:190: Warning log "Connection with stored token failed for %s: %v"
└── Call mcp.(*tokenStore).removeToken() <- THE BUG!
└── mcp.(*tokenStore).writeCredentials()
└── Writes empty map {} to mcp_oauth_tokens.json
Core Functions (Extracted from Binary Symbol Tables)
Inside connector.go, the connection failure error-handling logic behaves as follows:
// connector.go (Reconstructed logic)func (c*StreamableHTTPConnector) Connect(ctx, ...) error {
iftoken:=tokenStore.findCredentials(serverURL); token!=nil {
// Attempt connection with saved tokenerr:=c.tryConnectWithToken(ctx, serverURL, token)
iferr!=nil {
// BUG: Treating any error (including Bad Request) as token invalidationlog.Warningf("connector.go:190] Connection with stored token failed for %s: %v", serverName, err)
tokenStore.removeToken(serverURL) // Erroneously clears token!// Fall back to new OAuth authentication flowreturnc.initiateOAuthFlow(ctx, serverURL)
}
}
}
Why is this a bug?
The Notion MCP Server returns 400 Bad Request when receiving the notifications/roots/list_changed notification. This error is not a token invalidation issue; instead, it is caused by:
MCP Protocol Mismatch: The Notion server might not support or expect this specific protocol notification.
Transient Server-Side Issue: A temporary issue on Notion's servers.
Workspace Roots Change: The agy CLI was started from a different directory (e.g., /home/siubing instead of /home/siubing/code/python/mcp_test), resulting in a different workspace layout that the server rejected.
The error handler in connector.go fails to differentiate between 401 Unauthorized and other HTTP errors, resulting in removeToken() being called on any initialization warning.
WSL Environment Factors
token_storage.go:55] Using file-based token storage because WSL environment detected
In a WSL environment, agy falls back to file-based storage (mcp_oauth_tokens.json) instead of the system keyring. As a result, the removeToken() operation directly rewrites the local configuration file with an empty map ({}), making the token loss immediately visible on disk.
Complete Trigger Chain
[User starts agy CLI]
|
v
[WSL environment detected -> agy falls back to file-based token storage]
|
v
[User authenticates via ChainedAuth (xendomn@gmail.com)]
|
v
[MCP Manager loads Notion MCP configuration]
|
v
[McpManager.startSession() for "notion"]
|
v
[StreamableHTTPConnector.Connect()]
|
+-- tokenStore.findCredentials("https://mcp.notion.com/mcp") -> Finds saved token
|
v
[Initiate connection with stored token]
+-- Send MCP "initialize" request (Succeeds)
+-- Send "notifications/roots/list_changed" notification
| └── Notion Server returns: 400 Bad Request <- Failure point
|
v
[connector.go:190 logs warning: "Connection with stored token failed for notion"]
|
v
[BUG: tokenStore.removeToken("https://mcp.notion.com/mcp")]
|
v
[tokenStore.writeCredentials({}) -> Writes empty map to mcp_oauth_tokens.json]
|
v
[Token file is wiped -> contains only "{}"]
|
v
[Connector triggers new OAuth login flow, forcing user to re-authenticate]
In connector.go, only trigger removeToken() if the error is explicitly an authentication/authorization failure (e.g., 401 Unauthorized):
// Proposed FixifisUnauthorized(err) {
// Clear token only if it has actually expired or is invalidtokenStore.removeToken(serverURL)
returnc.initiateOAuthFlow(ctx, serverURL)
} else {
// For other transport/protocol errors (e.g., Bad Request), preserve the tokenlog.Warningf("MCP connection failed (token preserved): %v", err)
returnerr
}
Option 2: Ignore Errors on Non-Critical Notifications
Failure to deliver the notifications/roots/list_changed notification should not invalidate the entire session credentials. These secondary notifications should be logged as warnings without discarding the token.
Option 3: Implement Token Backup
Create a backup of the token before discarding it:
Description
Every time
agyCLI is restarted in WSL, the token storage file~/.gemini/antigravity-cli/mcp_oauth_tokens.jsonis cleared to{}. This forces the user to re-authenticate with Notion and other MCP Servers on every single launch.Root Cause
When the MCP manager attempts to connect using the stored token and receives a
400 Bad Requestfrom the Notion server during thenotifications/roots/list_changedmessage phase,connector.gotreats ANY error as token invalidation and callstokenStore.removeToken(serverURL).Complete Analysis Report
Please see the pasted bug analysis below for the detailed call stack, timeline logs, and recommended fix.
Bug Analysis Report:
agyClears MCP OAuth Token File on Every RestartCaution
This bug has been confirmed across 8 separate session logs. Every time
agyCLI is launched (with a Notion MCP server configured), the local OAuth credentials file is completely cleared.Issue Overview
Whenever
agyCLI restarts in WSL (Windows Subsystem for Linux), the token storage file~/.gemini/antigravity-cli/mcp_oauth_tokens.jsonis cleared to{}. This results in the loss of the OAuth token for the Notion MCP Server, forcing the user to re-authenticate on every launch.Key Evidence
1. Token File Status
mcp_oauth_tokens.json21:00:09(After new session startup){}(Cleared)mcp_oauth_tokens.json.back20:59:55(At the end of previous session)2. Reproduction History (All Confirmed Sessions)
Connection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad RequestConnection with stored token failed for notion: ... Bad Request100% Reproduction Rate: All
agysessions configured with the Notion MCP server trigger this bug on startup.3. Precise Timeline (Extracted from Logs)
Important
The modification timestamp of the token file (
21:00:09.206) is almost identical to the timestamp of the warning log inconnector.go:190(21:00:09.207). This 1ms interval demonstrates that the connection failure immediately triggers the token deletion logic inconnector.go.Two Distinct MCP Failure Modes
Analyzing the logs reveals two completely different MCP failure patterns:
400 Bad Requestnotifications/roots/list_changed401 Unauthorizedtools/callorinitializeWhile
401 Unauthorizedis the correct indicator of token expiration,400 Bad Request(which is typically a protocol or server-side error) is being incorrectly treated as a token invalidation trigger.Root Cause: Treating "Bad Request" as Token Invalidation
Code Call Hierarchy
Reconstructing the Go package symbols from the binary PCLN tables reveals the following call stack:
Core Functions (Extracted from Binary Symbol Tables)
Bug Details
Inside
connector.go, the connection failure error-handling logic behaves as follows:Why is this a bug?
The Notion MCP Server returns
400 Bad Requestwhen receiving thenotifications/roots/list_changednotification. This error is not a token invalidation issue; instead, it is caused by:agyCLI was started from a different directory (e.g.,/home/siubinginstead of/home/siubing/code/python/mcp_test), resulting in a different workspace layout that the server rejected.The error handler in
connector.gofails to differentiate between401 Unauthorizedand other HTTP errors, resulting inremoveToken()being called on any initialization warning.WSL Environment Factors
In a WSL environment,
agyfalls back to file-based storage (mcp_oauth_tokens.json) instead of the system keyring. As a result, theremoveToken()operation directly rewrites the local configuration file with an empty map ({}), making the token loss immediately visible on disk.Complete Trigger Chain
Recommended Fixes
Option 1: Differentiate Connection Errors (Recommended)
In
connector.go, only triggerremoveToken()if the error is explicitly an authentication/authorization failure (e.g.,401 Unauthorized):Option 2: Ignore Errors on Non-Critical Notifications
Failure to deliver the
notifications/roots/list_changednotification should not invalidate the entire session credentials. These secondary notifications should be logged as warnings without discarding the token.Option 3: Implement Token Backup
Create a backup of the token before discarding it:
Summary
third_party/jetski/cortex/utils/mcp/connector.go:190400 Bad Requestduringnotifications/roots/list_changedremoveToken()mcp_oauth_tokens.jsonis overwritten with{}on every CLI restart401 Unauthorizedvs other status codes)cp mcp_oauth_tokens.json.back mcp_oauth_tokens.json