@@ -64,13 +64,21 @@ func saveMcpProfile(profile *McpProfile) error {
6464 return fmt .Errorf ("failed to create config directory %q: %w" , dir , err )
6565 }
6666
67+ // log.Printf("saveMcpProfile: Before marshaling - RefreshToken length=%d, RefreshToken empty=%v",
68+ // len(profile.MCPOAuthRefreshToken), profile.MCPOAuthRefreshToken == "")
69+
6770 tempFile := mcpConfigPath + ".tmp"
6871
6972 bytes , err := json .MarshalIndent (profile , "" , "\t " )
7073 if err != nil {
7174 return fmt .Errorf ("failed to marshal profile: %w" , err )
7275 }
7376
77+ // jsonStr := string(bytes)
78+ // hasRefreshToken := strings.Contains(jsonStr, "mcp_oauth_refresh_token")
79+ // log.Printf("saveMcpProfile: After marshaling - JSON contains 'mcp_oauth_refresh_token'=%v, JSON length=%d",
80+ // hasRefreshToken, len(jsonStr))
81+
7482 if err := os .WriteFile (tempFile , bytes , 0600 ); err != nil {
7583 return fmt .Errorf ("failed to write temp file %q: %w" , tempFile , err )
7684 }
@@ -80,64 +88,121 @@ func saveMcpProfile(profile *McpProfile) error {
8088 _ = os .Remove (tempFile )
8189 return fmt .Errorf ("failed to rename temp file to %q: %w" , mcpConfigPath , err )
8290 }
91+
92+ log .Printf ("saveMcpProfile: Successfully saved MCP profile" )
8393 return nil
8494}
8595
96+ // loadExistingMCPProfile 加载并验证已有的 MCP profile,如果有效则返回,避免重复拉起 OAuth
97+ func loadExistingMCPProfile (ctx * cli.Context , profile config.Profile , opts ProxyConfig , desiredAppName string ) * McpProfile {
98+ mcpConfigPath := getMCPConfigPath ()
99+ bytes , err := os .ReadFile (mcpConfigPath )
100+ if err != nil {
101+ return nil
102+ }
103+ mcpProfile , err := NewMcpProfileFromBytes (bytes )
104+ if err != nil {
105+ return nil
106+ }
107+
108+ if mcpProfile .MCPOAuthSiteType != string (opts .RegionType ) {
109+ log .Printf ("Region type mismatch: saved=%s, requested=%s, ignoring local profile" , mcpProfile .MCPOAuthSiteType , string (opts .RegionType ))
110+ return nil
111+ }
112+
113+ if mcpProfile .MCPOAuthAppName != desiredAppName {
114+ log .Printf ("App name mismatch: saved=%s, requested=%s, ignoring local profile" , mcpProfile .MCPOAuthAppName , desiredAppName )
115+ return nil
116+ }
117+
118+ if mcpProfile .MCPOAuthAppId == "" {
119+ log .Printf ("MCP profile with AppId is empty, ignoring local profile" )
120+ return nil
121+ }
122+
123+ if mcpProfile .MCPOAuthRefreshToken == "" {
124+ log .Printf ("MCP profile with RefreshToken is empty, ignoring local profile" )
125+ return nil
126+ }
127+
128+ if mcpProfile .MCPOAuthRefreshTokenExpire <= util .GetCurrentUnixTime () {
129+ log .Printf ("MCP profile with RefreshTokenExpire is expired, ignoring local profile" )
130+ return nil
131+ }
132+
133+ app , err := findOAuthApplicationById (ctx , profile , mcpProfile .MCPOAuthAppId , opts .RegionType )
134+ if err != nil {
135+ log .Printf ("Failed to reuse existing MCP profile (app: %s): %v, ignoring local profile" , mcpProfile .MCPOAuthAppName , err )
136+ return nil
137+ }
138+ if app == nil {
139+ log .Printf ("OAuth application with AppId '%s' not found, ignoring local profile" , mcpProfile .MCPOAuthAppId )
140+ return nil
141+ }
142+
143+ if err := validateOAuthApplication (app , opts .Scope , opts .Host , opts .Port ); err != nil {
144+ log .Printf ("Reused existing MCP profile validation failed: %v, ignoring local profile" , err )
145+ return nil
146+ }
147+
148+ // 根据远端 app 信息更新 mcp profile 中的相关字段,其他字段(如 token)保持不变
149+ mcpProfile .MCPOAuthAppName = app .AppName
150+ mcpProfile .MCPOAuthAppId = app .ApplicationId
151+ mcpProfile .MCPOAuthAccessTokenValidity = app .AccessTokenValidity
152+ mcpProfile .MCPOAuthRefreshTokenValidity = app .RefreshTokenValidity
153+
154+ log .Printf ("Reused existing MCP profile with app '%s' (AppId: %s)" , app .AppName , app .ApplicationId )
155+
156+ return mcpProfile
157+ }
158+
86159func getOrCreateMCPProfile (ctx * cli.Context , opts ProxyConfig ) (* McpProfile , error ) {
87160 profile , err := config .LoadProfileWithContext (ctx )
88161 if err != nil {
89162 return nil , fmt .Errorf ("failed to load profile: %w" , err )
90163 }
91164
92- // 如果传入了 oauth-app-name,先验证该应用是否存在且合法
93- // 如果已经验证过 oauth-app-name,直接使用验证过的 app;否则查找或创建默认的 OAuth 应用
94- var validatedApp * OAuthApplication
95- if opts .OAuthAppName != "" {
96- app , err := findOAuthApplicationByName (ctx , profile , opts .RegionType , opts .OAuthAppName )
97- if err != nil {
98- return nil , fmt .Errorf ("failed to find OAuth application '%s': %w" , opts .OAuthAppName , err )
99- }
100- if app == nil {
101- return nil , fmt .Errorf ("OAuth application '%s' not found" , opts .OAuthAppName )
102- }
165+ // 如果未显式指定 app name,则使用默认的 MCPOAuthAppName,便于复用本地 profile
166+ desiredAppName := opts .OAuthAppName
167+ if desiredAppName == "" {
168+ desiredAppName = MCPOAuthAppName
169+ }
103170
104- // 验证 Scopes 和 Callback URI
105- requiredRedirectURI := buildRedirectUri (opts .Host , opts .Port )
106- if err := validateOAuthApplication (app , opts .Scope , requiredRedirectURI ); err != nil {
107- return nil , fmt .Errorf ("OAuth application validation failed: %w" , err )
171+ existingMcpProfile := loadExistingMCPProfile (ctx , profile , opts , desiredAppName )
172+ if existingMcpProfile != nil {
173+ // mcpprofile might change, save it again to ensure the latest state is saved
174+ if err := saveMcpProfile (existingMcpProfile ); err != nil {
175+ return nil , fmt .Errorf ("failed to save mcp profile: %w" , err )
108176 }
177+ return existingMcpProfile , nil
178+ }
109179
110- validatedApp = app
111- cli .Printf (ctx .Stdout (), "Using existing OAuth application '%s' (AppId: %s)\n " , app .AppName , app .ApplicationId )
112- } else {
113- // 查找或创建默认的 OAuth 应用
114- mcpConfigPath := getMCPConfigPath ()
115- if bytes , err := os .ReadFile (mcpConfigPath ); err == nil {
116- if mcpProfile , err := NewMcpProfileFromBytes (bytes ); err == nil {
117- log .Println ("MCP Profile loaded from file" , mcpProfile .Name , "app id" , mcpProfile .MCPOAuthAppId )
118-
119- // 检查 region type 是否匹配,因为国内和国际站的 OAuth 地址不同, Region type 不匹配则重新创建 profile
120- if mcpProfile .MCPOAuthSiteType != string (opts .RegionType ) {
121- log .Printf ("Region type mismatch: saved=%s, requested=%s, recreating profile" , mcpProfile .MCPOAuthSiteType , string (opts .RegionType ))
122- } else {
123- err = findOAuthApplicationById (ctx , profile , mcpProfile , opts .RegionType )
124- if err == nil {
125- return mcpProfile , nil
126- } else {
127- log .Println ("Failed to find existing OAuth application" , err .Error ())
128- }
129- }
130- }
180+ app , err := findOAuthApplicationByName (ctx , profile , opts .RegionType , desiredAppName )
181+ if err != nil {
182+ return nil , fmt .Errorf ("failed to find OAuth application '%s': %w" , desiredAppName , err )
183+ }
184+
185+ if app == nil {
186+ if opts .OAuthAppName != "" {
187+ // if user provide app name, but not found, return error
188+ return nil , fmt .Errorf ("OAuth application '%s' not found" , opts .OAuthAppName )
131189 }
132- app , err := getOrCreateMCPOAuthApplication (ctx , profile , opts .RegionType , opts .Host , opts .Port , opts .Scope )
190+ cli .Printf (ctx .Stdout (), "Creating new default MCP profile '%s'...\n " , DefaultMcpProfileName )
191+ app , err = createDefaultMCPOauthApplication (ctx , profile , opts .RegionType , opts .Host , opts .Port , opts .Scope )
133192 if err != nil {
134- return nil , fmt .Errorf ("failed to get or create OAuth application: %w" , err )
193+ return nil , fmt .Errorf ("failed to create default OAuth application: %w" , err )
135194 }
136- validatedApp = app
195+ cli .Printf (ctx .Stdout (), "Created new default OAuth application '%s' (AppId: %s)\n " , app .AppName , app .ApplicationId )
196+ } else {
197+ cli .Printf (ctx .Stdout (), "Using existing OAuth application '%s' (AppId: %s)\n " , app .AppName , app .ApplicationId )
137198 }
138199
139- cli .Printf (ctx .Stdout (), "Setting up MCPOAuth profile '%s'...\n " , DefaultMcpProfileName )
200+ if err := validateOAuthApplication (app , opts .Scope , opts .Host , opts .Port ); err != nil {
201+ return nil , fmt .Errorf ("OAuth application validation failed: %w" , err )
202+ }
203+ validatedApp := app
140204
205+ cli .Printf (ctx .Stdout (), "Setting up MCPOAuth profile '%s'...\n " , DefaultMcpProfileName )
141206 mcpProfile := NewMcpProfile (DefaultMcpProfileName )
142207 mcpProfile .MCPOAuthSiteType = string (opts .RegionType )
143208 mcpProfile .MCPOAuthAppId = validatedApp .ApplicationId
@@ -153,9 +218,20 @@ func getOrCreateMCPProfile(ctx *cli.Context, opts ProxyConfig) (*McpProfile, err
153218 if err != nil {
154219 return nil , fmt .Errorf ("OAuth login failed: %w" , err )
155220 }
221+
222+ log .Printf ("OAuth flow completed: AccessToken length=%d, RefreshToken length=%d, AccessTokenExpire=%d" ,
223+ len (tokenResult .AccessToken ), len (tokenResult .RefreshToken ), tokenResult .AccessTokenExpire )
224+ if tokenResult .RefreshToken == "" {
225+ return nil , fmt .Errorf ("OAuth flow returned empty RefreshToken (Region=%s, AppId=%s). " +
226+ "Please delete this application and let the system create a new NativeApp, or manually create a NativeApp" ,
227+ opts .RegionType , mcpProfile .MCPOAuthAppId )
228+ }
229+
156230 mcpProfile .MCPOAuthAccessToken = tokenResult .AccessToken
157231 mcpProfile .MCPOAuthRefreshToken = tokenResult .RefreshToken
158232 mcpProfile .MCPOAuthAccessTokenExpire = tokenResult .AccessTokenExpire
233+ // refresh token will be updated each time latest access token is refreshed,
234+ // however the validity and expiration time is the same as the original when finishing oauth flow
159235 mcpProfile .MCPOAuthRefreshTokenExpire = currentTime + int64 (validatedApp .RefreshTokenValidity )
160236
161237 if err = saveMcpProfile (mcpProfile ); err != nil {
0 commit comments