From 88d7e7ef1dd8055d7855926ee21d70326787df6f Mon Sep 17 00:00:00 2001 From: tt-a1i <53142663+tt-a1i@users.noreply.github.com> Date: Fri, 19 Jun 2026 09:03:08 +0800 Subject: [PATCH] fix(core): create token file on first save (#5367) --- .../token-storage/file-token-storage.test.ts | 33 +++++++++++++++++++ .../mcp/token-storage/file-token-storage.ts | 9 +++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/core/src/mcp/token-storage/file-token-storage.test.ts b/packages/core/src/mcp/token-storage/file-token-storage.test.ts index 53e7bb7c43..05587267f4 100644 --- a/packages/core/src/mcp/token-storage/file-token-storage.test.ts +++ b/packages/core/src/mcp/token-storage/file-token-storage.test.ts @@ -120,6 +120,39 @@ describe('FileTokenStorage', () => { }); describe('setCredentials', () => { + it('should create token file when saving credentials for the first time', async () => { + mockFs.readFile.mockRejectedValue({ code: 'ENOENT' }); + mockFs.mkdir.mockResolvedValue(undefined); + vi.mocked(atomicWriteFile).mockResolvedValue(undefined); + + const credentials: OAuthCredentials = { + serverName: 'test-server', + token: { + accessToken: 'access-token', + tokenType: 'Bearer', + }, + updatedAt: Date.now() - 10000, + }; + + await storage.setCredentials(credentials); + + expect(mockFs.mkdir).toHaveBeenCalledWith( + path.join('/home/test', '.qwen'), + { recursive: true, mode: 0o700 }, + ); + expect(atomicWriteFile).toHaveBeenCalled(); + + const writeCall = vi.mocked(atomicWriteFile).mock.calls[0]; + const decrypted = storage['decrypt'](writeCall[1] as string); + const saved = JSON.parse(decrypted); + + expect(Object.keys(saved)).toEqual(['test-server']); + expect(saved['test-server']).toEqual({ + ...credentials, + updatedAt: expect.any(Number), + }); + }); + it('should save credentials with encryption', async () => { const encryptedData = storage['encrypt']( JSON.stringify({ 'existing-server': existingCredentials }), diff --git a/packages/core/src/mcp/token-storage/file-token-storage.ts b/packages/core/src/mcp/token-storage/file-token-storage.ts index 03a88d956b..8c0828c5b5 100644 --- a/packages/core/src/mcp/token-storage/file-token-storage.ts +++ b/packages/core/src/mcp/token-storage/file-token-storage.ts @@ -78,7 +78,9 @@ export class FileTokenStorage await fs.mkdir(dir, { recursive: true, mode: 0o700 }); } - private async loadTokens(): Promise> { + private async loadTokens( + allowMissing = false, + ): Promise> { try { const data = await fs.readFile(this.tokenFilePath, 'utf-8'); const decrypted = this.decrypt(data); @@ -87,6 +89,9 @@ export class FileTokenStorage } catch (error: unknown) { const err = error as NodeJS.ErrnoException & { message?: string }; if (err.code === 'ENOENT') { + if (allowMissing) { + return new Map(); + } throw new Error('Token file does not exist'); } if ( @@ -135,7 +140,7 @@ export class FileTokenStorage async setCredentials(credentials: OAuthCredentials): Promise { this.validateCredentials(credentials); - const tokens = await this.loadTokens(); + const tokens = await this.loadTokens(true); const updatedCredentials: OAuthCredentials = { ...credentials, updatedAt: Date.now(),