chaimi-bookkeeping-mcp 3.1.13 → 3.1.15

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/bin/cli.js +4 -1
  2. package/package.json +1 -1
  3. package/server.js +53 -45
package/bin/cli.js CHANGED
@@ -25,7 +25,10 @@ const DEFAULT_CHAIMI_CONFIG = {
25
25
  MCP_OAUTH_URL: 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpOAuth',
26
26
  MCP_HUB_URL: 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpHub-mcp',
27
27
  MCP_PROMPT_URL: 'https://cloud1-2gfe5jhjef06b85d-1412172089.ap-shanghai.app.tcloudbase.com/mcpPrompt'
28
- }
28
+ },
29
+ // 首次授权时需要常驻运行,等待用户授权(3分钟轮询)
30
+ // 授权成功后进程自动退出,下次调用时 mcporter 会重新启动
31
+ lifecycle: 'keep-alive'
29
32
  };
30
33
 
31
34
  // 支持的 Agent 列表
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chaimi-bookkeeping-mcp",
3
- "version": "3.1.13",
3
+ "version": "3.1.15",
4
4
  "description": "柴米记账 MCP Server - 支持 Claude、Cursor、OpenClaw、WorkBuddy 等 AI 工具直接记账",
5
5
  "main": "server.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -241,7 +241,7 @@ let lastRefreshTime = 0;
241
241
  async function getToken() {
242
242
  // 检查是否已授权
243
243
  if (!authState.isAuthorized) {
244
- // 先检查是否有已保存的授权状态(从文件加载)
244
+ // 先检查是否有保存的授权状态(从文件加载)
245
245
  const savedAuthState = await oauthManager.loadAuthState();
246
246
  if (savedAuthState && savedAuthState.deviceCode) {
247
247
  // 有保存的授权状态,尝试直接获取token(用户可能已授权)
@@ -252,38 +252,28 @@ async function getToken() {
252
252
  cachedToken = token.accessToken;
253
253
  tokenExpireTime = token.expiresAt;
254
254
  authState.isAuthorized = true;
255
- authState.isWaiting = false;
255
+ await oauthManager.tokenStorage.save(token);
256
256
  await oauthManager.clearAuthState();
257
257
  console.error('✅ 授权成功!可以继续使用记账功能');
258
258
  return cachedToken;
259
259
  }
260
+ // 用户还未授权,返回同一个验证码
261
+ authState.deviceCode = savedAuthState.deviceCode;
262
+ authState.userCode = savedAuthState.userCode;
263
+ throw new Error(`NEED_AUTH:${savedAuthState.userCode}`);
260
264
  } catch (err) {
261
- // 获取失败,继续启动后台授权
265
+ if (err.message.startsWith('NEED_AUTH:')) {
266
+ throw err;
267
+ }
268
+ // 其他错误,继续获取新的验证码
262
269
  console.error('⚠️ 检查授权状态失败:', err.message);
263
270
  }
264
271
  }
265
272
 
266
- // 未授权,启动后台授权流程
267
- if (!authState.isWaiting) {
268
- await startBackgroundAuth();
269
- }
270
-
271
- // 等待一段时间,让后台轮询有机会完成授权
272
- // 最多等待10秒,每500ms检查一次
273
- let waitAttempts = 0;
274
- const maxWaitAttempts = 20; // 10秒
273
+ // 没有保存的授权状态,启动新的授权流程
274
+ await startAuthFlow();
275
275
 
276
- while (waitAttempts < maxWaitAttempts && authState.isWaiting && !authState.isAuthorized) {
277
- await delay(500);
278
- waitAttempts++;
279
- }
280
-
281
- // 再次检查是否已授权
282
- if (authState.isAuthorized) {
283
- return cachedToken;
284
- }
285
-
286
- // 仍未授权,返回验证码给Agent
276
+ // 返回验证码给Agent
287
277
  throw new Error(`NEED_AUTH:${authState.userCode || '等待生成验证码'}`);
288
278
  }
289
279
 
@@ -805,22 +795,29 @@ async function checkAuthInBackground() {
805
795
  }
806
796
  }
807
797
 
808
- // 启动后台授权流程(非阻塞)
809
- async function startBackgroundAuth() {
810
- if (authState.isWaiting) {
811
- return; // 已经在等待授权
812
- }
813
-
798
+ // 启动授权流程(获取验证码,启动后台轮询)
799
+ async function startAuthFlow() {
814
800
  try {
815
- authState.isWaiting = true;
816
- authState.error = null;
801
+ // 先检查是否有保存的授权状态
802
+ const savedAuthState = await oauthManager.loadAuthState();
803
+ if (savedAuthState && savedAuthState.userCode) {
804
+ // 有保存的验证码,直接使用
805
+ authState.deviceCode = savedAuthState.deviceCode;
806
+ authState.userCode = savedAuthState.userCode;
807
+ authState.isWaiting = true;
808
+ console.error(`🔑 使用已保存的验证码:${savedAuthState.userCode}`);
809
+ // 启动后台轮询
810
+ pollForAuthInBackground(savedAuthState.deviceCode, 5000);
811
+ return;
812
+ }
817
813
 
818
- // 获取设备码
814
+ // 获取新的设备码
819
815
  const deviceCodeRes = await oauthManager.requestDeviceCode(false);
820
816
  authState.deviceCode = deviceCodeRes.deviceCode;
821
817
  authState.userCode = deviceCodeRes.userCode;
818
+ authState.isWaiting = true;
822
819
 
823
- // 保存授权状态
820
+ // 保存授权状态(30分钟有效)
824
821
  await oauthManager.saveAuthState({
825
822
  deviceCode: deviceCodeRes.deviceCode,
826
823
  userCode: deviceCodeRes.userCode,
@@ -828,10 +825,10 @@ async function startBackgroundAuth() {
828
825
  });
829
826
 
830
827
  console.error(`🔑 验证码:${deviceCodeRes.userCode}`);
831
- console.error('⏳ 等待用户授权中...');
832
-
833
- // 后台轮询(不阻塞)
834
- pollForAuthInBackground(deviceCodeRes.deviceCode, deviceCodeRes.interval * 1000 || 5000);
828
+ console.error('⏳ 请在小程序中完成授权,然后再次调用');
829
+
830
+ // 启动后台轮询(3分钟)
831
+ pollForAuthInBackground(deviceCodeRes.deviceCode, 5000);
835
832
 
836
833
  } catch (err) {
837
834
  authState.error = err.message;
@@ -840,42 +837,53 @@ async function startBackgroundAuth() {
840
837
  }
841
838
  }
842
839
 
843
- // 后台轮询授权状态(简化版,只轮询几次就停止,因为mcporter 30秒超时)
840
+ // 后台轮询授权状态(3分钟)
844
841
  async function pollForAuthInBackground(deviceCode, interval) {
845
- const maxAttempts = 5; // 只轮询5次(25秒),因为mcporter 30秒超时
842
+ const maxAttempts = 36; // 3分钟(36次 × 5秒)
846
843
  let attempts = 0;
847
844
 
845
+ console.error('⏳ 等待用户授权中...(3分钟内有效)');
846
+
848
847
  while (attempts < maxAttempts && authState.isWaiting) {
849
848
  attempts++;
850
849
  await delay(interval);
851
850
 
852
851
  try {
852
+ // 调用云函数检查授权状态
853
853
  const token = await oauthManager.pollForTokenOnce(deviceCode);
854
+
854
855
  if (token) {
855
856
  // 授权成功
856
857
  cachedToken = token.accessToken;
857
858
  tokenExpireTime = token.expiresAt;
858
859
  authState.isAuthorized = true;
859
860
  authState.isWaiting = false;
860
- await oauthManager.tokenStorage.save(token); // 保存token到文件
861
+
862
+ // 保存token到文件
863
+ await oauthManager.tokenStorage.save(token);
864
+ // 清除授权状态
861
865
  await oauthManager.clearAuthState();
862
- console.error('✅ 授权成功!可以继续使用记账功能');
863
- return;
866
+
867
+ console.error('✅ 授权成功!下次调用将快速启动');
868
+
869
+ // 主动退出进程,让 mcporter 下次重新启动
870
+ process.exit(0);
864
871
  }
865
872
  } catch (err) {
866
873
  if (err.message.includes('expired') || err.message.includes('invalid')) {
867
874
  authState.error = err.message;
868
875
  authState.isWaiting = false;
869
876
  console.error('❌ 授权失败:', err.message);
870
- return;
877
+ process.exit(1);
871
878
  }
872
879
  // 继续轮询
873
880
  }
874
881
  }
875
882
 
876
- // 轮询结束(可能是超时或达到最大次数)
883
+ // 3分钟超时
877
884
  authState.isWaiting = false;
878
- console.error(' 授权检查完成,如果已授权请再次调用');
885
+ console.error(' 授权超时,请再次调用获取新验证码');
886
+ process.exit(0);
879
887
  }
880
888
 
881
889
  // 延迟函数