chaimi-keep-mcp 3.1.46-beta.0 → 3.1.46-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/package.json +1 -1
  3. package/server.js +128 -147
package/README.md CHANGED
@@ -141,6 +141,14 @@ AI 会自动调用 `save_income` 工具记录收入。
141
141
 
142
142
  ## 更新日志
143
143
 
144
+ ### v3.1.46-beta.0 (2026-04-22)
145
+ - **修复** 授权流程阻塞问题:恢复到 v3.1.37 之前的非阻塞模式
146
+ - 移除 `startAuthFlowAndWait()` 和 `waitForAuthWithPolling()` 阻塞函数
147
+ - 恢复 `startAuthFlow()` + `pollForAuthInBackground()` 非阻塞模式
148
+ - 立即返回验证码给 Agent,配合 `lifecycle: keep-alive` 后台轮询
149
+ - 解决 mcporter 超时和验证码不显示问题
150
+ - **基于** npm 3.1.44 版本,保留其他所有功能和修复
151
+
144
152
  ### v3.1.44 (2026-04-21) 🎉 正式版
145
153
  - **正式发布** 包含所有 beta 版本修复
146
154
  - MCP 调用日志字段缺失修复
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-keep-mcp",
3
- "version": "3.1.46-beta.0",
3
+ "version": "3.1.46-beta.1",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -376,7 +376,52 @@ const TOKEN_REFRESH_INTERVAL = 2 * 60 * 60 * 1000;
376
376
  let lastRefreshTime = 0;
377
377
 
378
378
  async function getToken() {
379
- // 步骤1:检查内存缓存 Token(最快路径)
379
+ // 检查是否已授权
380
+ if (!authState.isAuthorized) {
381
+ // 先检查是否有保存的授权状态(从文件加载)
382
+ const savedAuthState = await oauthManager.loadAuthState();
383
+ if (savedAuthState && savedAuthState.deviceCode) {
384
+ // 检查验证码是否已过期
385
+ if (oauthManager.isAuthStateExpired(savedAuthState)) {
386
+ console.error('⏰ 保存的验证码已过期,清除并生成新的验证码');
387
+ await oauthManager.clearAuthState();
388
+ // 继续执行,生成新的验证码
389
+ } else {
390
+ // 验证码未过期,尝试直接获取token(用户可能已授权)
391
+ try {
392
+ const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode);
393
+ if (token) {
394
+ // 授权成功
395
+ cachedToken = token.accessToken;
396
+ tokenExpireTime = token.expiresAt;
397
+ authState.isAuthorized = true;
398
+ await oauthManager.tokenStorage.save(token);
399
+ await oauthManager.clearAuthState();
400
+ console.error('✅ 授权成功!可以继续使用记账功能');
401
+ // 返回特殊标记,让上层知道这是刚完成授权的情况
402
+ throw new Error(`AUTH_JUST_COMPLETED:${cachedToken}`);
403
+ }
404
+ // 用户还未授权,返回同一个验证码
405
+ authState.deviceCode = savedAuthState.deviceCode;
406
+ authState.userCode = savedAuthState.userCode;
407
+ throw new Error(`NEED_AUTH:${savedAuthState.userCode}`);
408
+ } catch (err) {
409
+ if (err.message.startsWith('NEED_AUTH:') || err.message.startsWith('AUTH_JUST_COMPLETED:')) {
410
+ throw err;
411
+ }
412
+ // 其他错误,继续获取新的验证码
413
+ console.error('⚠️ 检查授权状态失败:', err.message);
414
+ }
415
+ }
416
+ }
417
+
418
+ // 没有保存的授权状态或已过期,启动新的授权流程
419
+ await startAuthFlow();
420
+
421
+ // 返回验证码给Agent
422
+ throw new Error(`NEED_AUTH:${authState.userCode || '等待生成验证码'}`);
423
+ }
424
+
380
425
  if (cachedToken && tokenExpireTime > Date.now() + 5 * 60 * 1000) {
381
426
  if (Date.now() - lastRefreshTime < TOKEN_REFRESH_INTERVAL) {
382
427
  return cachedToken;
@@ -387,76 +432,18 @@ async function getToken() {
387
432
  throw new Error('OAuth 管理器未初始化,请检查 MCP_OAUTH_URL 配置');
388
433
  }
389
434
 
390
- // 步骤2:尝试从文件加载并刷新 Token
391
435
  try {
392
436
  const oauthToken = await oauthManager.getValidToken();
393
437
  cachedToken = oauthToken.accessToken;
394
438
  tokenExpireTime = oauthToken.expiresAt;
395
439
  lastRefreshTime = Date.now();
396
- authState.isAuthorized = true;
397
440
  await oauthManager.clearAuthState();
398
441
  return cachedToken;
399
442
  } catch (err) {
400
- // Token 无效,需要重新授权
401
- console.error('⏰ Token 无效,需要重新授权:', err.message);
402
- }
403
-
404
- // 步骤3:检查是否有保存的授权状态(用户可能正在授权中)
405
- const savedAuthState = await oauthManager.loadAuthState();
406
- if (savedAuthState && savedAuthState.deviceCode) {
407
- // 检查验证码是否已过期
408
- if (oauthManager.isAuthStateExpired(savedAuthState)) {
409
- console.error('⏰ 保存的验证码已过期,清除并生成新的验证码');
410
- await oauthManager.clearAuthState();
411
- } else {
412
- // 验证码未过期,尝试直接获取token(用户可能已授权)
413
- try {
414
- const token = await oauthManager.pollForTokenOnce(savedAuthState.deviceCode);
415
- if (token) {
416
- // 授权成功
417
- cachedToken = token.accessToken;
418
- tokenExpireTime = token.expiresAt;
419
- authState.isAuthorized = true;
420
- await oauthManager.tokenStorage.save(token);
421
- await oauthManager.clearAuthState();
422
- console.error('✅ 授权成功!可以继续使用记账功能');
423
- // 返回特殊标记,让上层知道这是刚完成授权的情况
424
- throw new Error(`AUTH_JUST_COMPLETED:${cachedToken}`);
425
- }
426
- // 用户还未授权,启动轮询等待
427
- console.error(`⏳ 等待用户授权,验证码:${savedAuthState.userCode}`);
428
- const authResult = await waitForAuthWithPolling(savedAuthState.deviceCode, savedAuthState.userCode, 120000);
429
- if (authResult.success) {
430
- cachedToken = authResult.token.accessToken;
431
- tokenExpireTime = authResult.token.expiresAt;
432
- authState.isAuthorized = true;
433
- await oauthManager.tokenStorage.save(authResult.token);
434
- await oauthManager.clearAuthState();
435
- return cachedToken;
436
- } else {
437
- throw new Error(`NEED_AUTH:${savedAuthState.userCode}`);
438
- }
439
- } catch (err) {
440
- if (err.message.startsWith('NEED_AUTH:') || err.message.startsWith('AUTH_JUST_COMPLETED:')) {
441
- throw err;
442
- }
443
- console.error('⚠️ 检查授权状态失败:', err.message);
444
- }
445
- }
446
- }
447
-
448
- // 步骤4:启动新的授权流程并等待
449
- console.error('🔐 启动新的授权流程...');
450
- const authResult = await startAuthFlowAndWait(120000);
451
-
452
- if (authResult.success) {
453
- cachedToken = authResult.token.accessToken;
454
- tokenExpireTime = authResult.token.expiresAt;
455
- authState.isAuthorized = true;
456
- return cachedToken;
457
- } else {
458
- // 超时或失败,抛出错误让 Agent 知道需要用户介入
459
- throw new Error(`NEED_AUTH:${authResult.userCode || '授权超时,请再次调用'}`);
443
+ // Token 获取失败,可能需要重新授权
444
+ authState.isAuthorized = false;
445
+ authState.isWaiting = false;
446
+ throw new Error(`NEED_AUTH:授权已过期,请重新授权`);
460
447
  }
461
448
  }
462
449
 
@@ -1823,112 +1810,106 @@ async function main() {
1823
1810
  await server.connect(transport);
1824
1811
  }
1825
1812
 
1826
- // 延迟函数
1827
- function delay(ms) {
1828
- return new Promise(resolve => setTimeout(resolve, ms));
1813
+ // 启动授权流程(获取验证码,启动后台轮询)
1814
+ async function startAuthFlow() {
1815
+ try {
1816
+ // 先检查是否有保存的授权状态
1817
+ const savedAuthState = await oauthManager.loadAuthState();
1818
+ if (savedAuthState && savedAuthState.userCode) {
1819
+ // 检查验证码是否过期
1820
+ if (oauthManager.isAuthStateExpired(savedAuthState)) {
1821
+ console.error('⏰ 验证码已过期,生成新的验证码');
1822
+ await oauthManager.clearAuthState();
1823
+ } else {
1824
+ // 有保存的验证码,直接使用
1825
+ authState.deviceCode = savedAuthState.deviceCode;
1826
+ authState.userCode = savedAuthState.userCode;
1827
+ authState.isWaiting = true;
1828
+ console.error(`🔑 使用已保存的验证码:${savedAuthState.userCode}`);
1829
+ // 启动后台轮询
1830
+ pollForAuthInBackground(savedAuthState.deviceCode, 5000);
1831
+ return;
1832
+ }
1833
+ }
1834
+
1835
+ // 获取新的设备码
1836
+ const deviceCodeRes = await oauthManager.requestDeviceCode(false);
1837
+ authState.deviceCode = deviceCodeRes.deviceCode;
1838
+ authState.userCode = deviceCodeRes.userCode;
1839
+ authState.isWaiting = true;
1840
+
1841
+ // 保存授权状态(30分钟有效)
1842
+ await oauthManager.saveAuthState({
1843
+ deviceCode: deviceCodeRes.deviceCode,
1844
+ userCode: deviceCodeRes.userCode,
1845
+ expiresAt: Date.now() + deviceCodeRes.expiresIn * 1000
1846
+ });
1847
+
1848
+ console.error(`🔑 验证码:${deviceCodeRes.userCode}`);
1849
+ console.error('⏳ 请在小程序中完成授权,然后再次调用');
1850
+
1851
+ // 启动后台轮询(3分钟)
1852
+ pollForAuthInBackground(deviceCodeRes.deviceCode, 5000);
1853
+
1854
+ } catch (err) {
1855
+ authState.error = err.message;
1856
+ authState.isWaiting = false;
1857
+ console.error('❌ 启动授权失败:', err.message);
1858
+ }
1829
1859
  }
1830
1860
 
1831
- // 等待授权完成(带轮询)- 方案三实现
1832
- async function waitForAuthWithPolling(deviceCode, userCode, timeout = 120000) {
1833
- const startTime = Date.now();
1834
- const interval = 5000; // 5秒轮询一次
1861
+ // 后台轮询授权状态(3分钟)
1862
+ async function pollForAuthInBackground(deviceCode, interval) {
1863
+ const maxAttempts = 36; // 3分钟(36次 × 5秒)
1864
+ let attempts = 0;
1835
1865
 
1836
- console.error(`⏳ 等待用户授权中...(验证码:${userCode})`);
1837
- console.error(' 请打开"柴米AI记账"小程序完成授权');
1866
+ console.error('⏳ 等待用户授权中...(3分钟内有效)');
1838
1867
 
1839
- while (Date.now() - startTime < timeout) {
1868
+ while (attempts < maxAttempts && authState.isWaiting) {
1869
+ attempts++;
1840
1870
  await delay(interval);
1841
1871
 
1842
1872
  try {
1843
1873
  // 调用云函数检查授权状态
1844
1874
  const token = await oauthManager.pollForTokenOnce(deviceCode);
1845
-
1875
+
1846
1876
  if (token) {
1847
1877
  // 授权成功
1848
- console.error('✅ 检测到用户授权成功!');
1849
- return { success: true, token };
1878
+ cachedToken = token.accessToken;
1879
+ tokenExpireTime = token.expiresAt;
1880
+ authState.isAuthorized = true;
1881
+ authState.isWaiting = false;
1882
+
1883
+ // 保存token到文件
1884
+ await oauthManager.tokenStorage.save(token);
1885
+ // 清除授权状态
1886
+ await oauthManager.clearAuthState();
1887
+
1888
+ console.error('✅ 授权成功!下次调用将快速启动');
1889
+
1890
+ // 主动退出进程,让 mcporter 下次重新启动
1891
+ process.exit(0);
1850
1892
  }
1851
1893
  } catch (err) {
1852
1894
  if (err.message.includes('expired') || err.message.includes('invalid')) {
1895
+ authState.error = err.message;
1896
+ authState.isWaiting = false;
1853
1897
  console.error('❌ 授权失败:', err.message);
1854
- return { success: false, error: err.message };
1898
+ process.exit(1);
1855
1899
  }
1856
1900
  // 继续轮询
1857
1901
  }
1858
-
1859
- // 输出等待提示(每30秒一次)
1860
- const elapsed = Math.floor((Date.now() - startTime) / 1000);
1861
- if (elapsed % 30 === 0) {
1862
- console.error(` 已等待 ${elapsed} 秒...`);
1863
- }
1864
1902
  }
1865
1903
 
1866
- // 超时
1867
- console.error('⏰ 等待授权超时(2分钟)');
1868
- return { success: false, error: 'timeout', userCode };
1904
+ // 3分钟超时
1905
+ authState.isWaiting = false;
1906
+ console.error('⏰ 授权超时,请再次调用获取新验证码');
1907
+ process.exit(0);
1869
1908
  }
1870
1909
 
1871
- // 启动授权流程并等待 - 方案三实现
1872
- async function startAuthFlowAndWait(timeout = 120000) {
1873
- try {
1874
- // 检查是否已有进行中的授权流程(防止并发)
1875
- if (authState.isWaiting) {
1876
- console.error('⏳ 已有进行中的授权流程,等待完成...');
1877
- // 等待现有流程完成
1878
- const startTime = Date.now();
1879
- while (authState.isWaiting && Date.now() - startTime < timeout) {
1880
- await delay(1000);
1881
- }
1882
- // 如果授权成功,返回成功
1883
- if (authState.isAuthorized && cachedToken) {
1884
- return { success: true, token: { accessToken: cachedToken, expiresAt: tokenExpireTime } };
1885
- }
1886
- // 否则继续启动新的授权流程
1887
- }
1888
-
1889
- // 设置等待状态(防止并发)
1890
- authState.isWaiting = true;
1891
- authState.error = null;
1892
-
1893
- // 获取新的设备码
1894
- const deviceCodeRes = await oauthManager.requestDeviceCode(false);
1895
- const deviceCode = deviceCodeRes.deviceCode;
1896
- const userCode = deviceCodeRes.userCode;
1897
-
1898
- // 保存授权状态(30分钟有效)
1899
- await oauthManager.saveAuthState({
1900
- deviceCode: deviceCode,
1901
- userCode: userCode,
1902
- expiresAt: Date.now() + deviceCodeRes.expiresIn * 1000
1903
- });
1904
-
1905
- console.error(`🔑 验证码:${userCode}`);
1906
- console.error('━━━━━━━━━━━━━━');
1907
- console.error('📱 请打开"柴米AI记账"小程序:');
1908
- console.error(' 1. 点击"我的" → "🤖 Agent 授权"');
1909
- console.error(` 2. 输入验证码:${userCode}`);
1910
- console.error(' 3. 点击确认授权');
1911
- console.error('━━━━━━━━━━━━━━');
1912
- console.error(`⏳ 等待您授权完成(${Math.floor(timeout / 1000)}秒内)...`);
1913
-
1914
- // 等待用户授权
1915
- const result = await waitForAuthWithPolling(deviceCode, userCode, timeout);
1916
-
1917
- // 清除等待状态
1918
- authState.isWaiting = false;
1919
-
1920
- if (result.success) {
1921
- return { success: true, token: result.token };
1922
- } else {
1923
- return { success: false, userCode, error: result.error };
1924
- }
1925
-
1926
- } catch (err) {
1927
- // 清除等待状态
1928
- authState.isWaiting = false;
1929
- console.error('❌ 启动授权流程失败:', err.message);
1930
- return { success: false, error: err.message };
1931
- }
1910
+ // 延迟函数
1911
+ function delay(ms) {
1912
+ return new Promise(resolve => setTimeout(resolve, ms));
1932
1913
  }
1933
1914
 
1934
1915
  /**