feishu-mcp 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -89,6 +89,7 @@
89
89
  - **版本更新通知**:在发布新版本时,及时向用户提供相关提示与说明。
90
90
  - ~~**stdio模式user认证问题**:修复stdio模式下飞书user认证失败问题~~ 0.1.9 ✅
91
91
  - ~~**权限检查功能可配置化**:将权限检查功能作为可配置选项,支持通过环境变量 `FEISHU_SCOPE_VALIDATION` 或命令行参数 `--feishu-scope-validation` 控制,默认启用,满足不同用户的使用场景~~ 0.2.0 ✅
92
+ - ~~**优化缓存目录:把token等缓存保存到系统级的配置目录~~ 0.2.2 ✅ 感谢 [Molunerfinn](https://github.com/Molunerfinn)、[leeeezx](https://github.com/leeeezx)、[Master-cai](https://github.com/Master-cai) 三位朋友的建议及代码贡献
92
93
  ---
93
94
 
94
95
  ## 🔧 飞书配置教程
@@ -160,6 +161,7 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
160
161
  | `PORT` | ❌ | 服务器端口 | `3333` |
161
162
  | `FEISHU_AUTH_TYPE` | ❌ | 认证凭证类型,使用 `user`(用户级,使用时是用户的身份操作飞书文档,需OAuth授权),使用 `tenant`(应用级,默认) | `tenant` |
162
163
  | `FEISHU_SCOPE_VALIDATION` | ❌ | 是否启用权限检查,设置为 `false` 可关闭权限检查(适用于仅使用部分功能的场景) | `true` |
164
+ | `FEISHU_USER_KEY` | ❌ | `stdio` 模式的用户标识,可通过命令行参数 `--user-key` 覆盖 | `stdio` |
163
165
 
164
166
  ### 配置文件方式(适用于 Cursor、Cline 等)
165
167
 
@@ -168,11 +170,12 @@ npx feishu-mcp@latest --feishu-app-id=<你的飞书应用ID> --feishu-app-secret
168
170
  "mcpServers": {
169
171
  "feishu-mcp": {
170
172
  "command": "npx",
171
- "args": ["-y", "feishu@latest", "--stdio"],
173
+ "args": ["-y", "feishu-mcp@latest", "--stdio"],
172
174
  "env": {
173
175
  "FEISHU_APP_ID": "<你的飞书应用ID>",
174
176
  "FEISHU_APP_SECRET": "<你的飞书应用密钥>",
175
- "FEISHU_AUTH_TYPE": "<tenant/user>"
177
+ "FEISHU_AUTH_TYPE": "<tenant/user>",
178
+ "FEISHU_USER_KEY": "<你的用户标识>"
176
179
  }
177
180
  },
178
181
  "feishu_local": {
@@ -6,7 +6,7 @@ import { registerFeishuBlockTools } from './tools/feishuBlockTools.js';
6
6
  import { registerFeishuFolderTools } from './tools/feishuFolderTools.js';
7
7
  export const serverInfo = {
8
8
  name: "Feishu MCP Server",
9
- version: "0.2.0",
9
+ version: "0.2.2",
10
10
  };
11
11
  const serverOptions = {
12
12
  capabilities: { logging: {}, tools: {} },
@@ -70,8 +70,8 @@ export class BaseApiService {
70
70
  let baseUrl;
71
71
  if (this.isStdioMode()) {
72
72
  // stdio 模式下直接使用默认值
73
- userKey = 'stdio';
74
73
  const config = Config.getInstance();
74
+ userKey = config.feishu.userKey || 'stdio';
75
75
  baseUrl = `http://localhost:${config.server.port}`;
76
76
  }
77
77
  else {
@@ -316,7 +316,7 @@ export class BaseApiService {
316
316
  // refresh_token已过期或不存在,直接清除缓存
317
317
  Logger.warn('用户模式:refresh_token已过期,清除用户token缓存');
318
318
  tokenCacheManager.removeUserToken(clientKey);
319
- return this.handleAuthFailure(true, clientKey, baseUrl, userKey);
319
+ return this.handleAuthFailure(false, clientKey, baseUrl, userKey);
320
320
  }
321
321
  }
322
322
  /**
@@ -0,0 +1,56 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { Logger } from '../logger.js';
4
+ /** 旧版缓存文件名(de29caca 之前位于 process.cwd()) */
5
+ const LEGACY_FILES = [
6
+ 'user_token_cache.json',
7
+ 'tenant_token_cache.json',
8
+ 'scope_version_cache.json',
9
+ ];
10
+ const MIGRATION_MARKER = '.legacy_cache_migrated';
11
+ /**
12
+ * 将旧路径(process.cwd())下的 token 缓存迁移到新目录:复制内容到新文件后删除原文件。
13
+ * 通过标记文件保证只执行一次;若新目录与 cwd 相同则跳过。
14
+ */
15
+ export function migrateLegacyTokenCacheIfNeeded(newCacheDir, newPaths) {
16
+ const markerPath = path.join(newCacheDir, MIGRATION_MARKER);
17
+ if (fs.existsSync(markerPath)) {
18
+ Logger.debug('Token 缓存已迁移过,跳过本次迁移');
19
+ return;
20
+ }
21
+ const legacyDir = process.cwd();
22
+ if (path.resolve(legacyDir) === path.resolve(newCacheDir)) {
23
+ Logger.debug('缓存目录与工作目录相同,无需迁移');
24
+ return;
25
+ }
26
+ Logger.info(`检查旧路径缓存并迁移: ${legacyDir} -> ${newCacheDir}`);
27
+ let migratedCount = 0;
28
+ for (let i = 0; i < LEGACY_FILES.length; i++) {
29
+ const oldPath = path.join(legacyDir, LEGACY_FILES[i]);
30
+ const newPath = newPaths[i];
31
+ if (!fs.existsSync(oldPath)) {
32
+ Logger.debug(`旧缓存不存在,跳过: ${LEGACY_FILES[i]}`);
33
+ continue;
34
+ }
35
+ try {
36
+ const content = fs.readFileSync(oldPath, 'utf-8');
37
+ fs.writeFileSync(newPath, content, 'utf-8');
38
+ fs.unlinkSync(oldPath);
39
+ migratedCount++;
40
+ Logger.info(`已迁移旧缓存并删除原文件: ${LEGACY_FILES[i]} -> ${newPath}`);
41
+ }
42
+ catch (error) {
43
+ Logger.warn(`迁移旧缓存失败,跳过 ${LEGACY_FILES[i]}`, error);
44
+ }
45
+ }
46
+ if (migratedCount > 0) {
47
+ Logger.info(`Token 缓存迁移完成,共 ${migratedCount} 个文件`);
48
+ }
49
+ try {
50
+ fs.writeFileSync(markerPath, new Date().toISOString(), 'utf-8');
51
+ Logger.debug(`迁移标记已写入: ${markerPath}`);
52
+ }
53
+ catch (error) {
54
+ Logger.warn('写入迁移标记失败', error);
55
+ }
56
+ }
@@ -1,6 +1,8 @@
1
1
  import * as fs from 'fs';
2
+ import * as os from 'os';
2
3
  import * as path from 'path';
3
4
  import { Logger } from '../logger.js';
5
+ import { migrateLegacyTokenCacheIfNeeded } from './legacyCacheMigration.js';
4
6
  /**
5
7
  * Token缓存管理器
6
8
  * 专门处理用户token和租户token的缓存管理
@@ -34,13 +36,46 @@ export class TokenCacheManager {
34
36
  writable: true,
35
37
  value: void 0
36
38
  });
39
+ Object.defineProperty(this, "cacheDir", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: void 0
44
+ });
37
45
  this.cache = new Map();
38
- this.userTokenCacheFile = path.resolve(process.cwd(), 'user_token_cache.json');
39
- this.tenantTokenCacheFile = path.resolve(process.cwd(), 'tenant_token_cache.json');
40
- this.scopeVersionCacheFile = path.resolve(process.cwd(), 'scope_version_cache.json');
46
+ this.cacheDir = this.resolveCacheDir();
47
+ this.userTokenCacheFile = path.join(this.cacheDir, 'user_token_cache.json');
48
+ this.tenantTokenCacheFile = path.join(this.cacheDir, 'tenant_token_cache.json');
49
+ this.scopeVersionCacheFile = path.join(this.cacheDir, 'scope_version_cache.json');
50
+ Logger.info(`Token缓存目录: ${this.cacheDir}`);
51
+ migrateLegacyTokenCacheIfNeeded(this.cacheDir, [
52
+ this.userTokenCacheFile,
53
+ this.tenantTokenCacheFile,
54
+ this.scopeVersionCacheFile,
55
+ ]);
41
56
  this.loadTokenCaches();
42
57
  this.startCacheCleanupTimer();
43
58
  }
59
+ /**
60
+ * 解析并确保可写的缓存目录
61
+ */
62
+ resolveCacheDir() {
63
+ const candidates = [
64
+ path.join(os.homedir(), '.cache', 'feishu-mcp'),
65
+ path.join(os.homedir(), '.feishu-mcp'),
66
+ ];
67
+ for (const dir of candidates) {
68
+ try {
69
+ fs.mkdirSync(dir, { recursive: true });
70
+ fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK);
71
+ return dir;
72
+ }
73
+ catch (error) {
74
+ Logger.warn(`缓存目录不可用,尝试下一个: ${dir}`, error);
75
+ }
76
+ }
77
+ throw new Error('无法找到可读写的缓存目录');
78
+ }
44
79
  /**
45
80
  * 获取TokenCacheManager实例
46
81
  */
@@ -123,6 +123,10 @@ export class Config {
123
123
  'feishu-scope-validation': {
124
124
  type: 'boolean',
125
125
  description: '是否启用权限检查,默认 true'
126
+ },
127
+ 'user-key': {
128
+ type: 'string',
129
+ description: 'stdio 模式下的用户标识,默认 stdio'
126
130
  }
127
131
  })
128
132
  .help()
@@ -166,6 +170,7 @@ export class Config {
166
170
  authType: 'tenant', // 默认
167
171
  tokenEndpoint: `http://127.0.0.1:${serverConfig.port}/getToken`, // 默认动态端口
168
172
  enableScopeValidation: true, // 默认启用权限检查
173
+ userKey: 'stdio',
169
174
  };
170
175
  // 处理App ID
171
176
  if (argv['feishu-app-id']) {
@@ -233,6 +238,18 @@ export class Config {
233
238
  else {
234
239
  this.configSources['feishu.enableScopeValidation'] = ConfigSource.DEFAULT;
235
240
  }
241
+ // 处理 userKey(stdio 模式使用)
242
+ if (argv['user-key']) {
243
+ feishuConfig.userKey = argv['user-key'];
244
+ this.configSources['feishu.userKey'] = ConfigSource.CLI;
245
+ }
246
+ else if (process.env.FEISHU_USER_KEY) {
247
+ feishuConfig.userKey = process.env.FEISHU_USER_KEY;
248
+ this.configSources['feishu.userKey'] = ConfigSource.ENV;
249
+ }
250
+ else {
251
+ this.configSources['feishu.userKey'] = ConfigSource.DEFAULT;
252
+ }
236
253
  return feishuConfig;
237
254
  }
238
255
  /**
@@ -367,6 +384,7 @@ export class Config {
367
384
  Logger.info(`- API URL: ${this.feishu.baseUrl} (来源: ${this.configSources['feishu.baseUrl']})`);
368
385
  Logger.info(`- 认证类型: ${this.feishu.authType} (来源: ${this.configSources['feishu.authType']})`);
369
386
  Logger.info(`- 启用权限检查: ${this.feishu.enableScopeValidation} (来源: ${this.configSources['feishu.enableScopeValidation']})`);
387
+ Logger.info(`- User Key: ${this.feishu.userKey} (来源: ${this.configSources['feishu.userKey']})`);
370
388
  Logger.info('日志配置:');
371
389
  Logger.info(`- 日志级别: ${LogLevel[this.log.level]} (来源: ${this.configSources['log.level']})`);
372
390
  Logger.info(`- 显示时间戳: ${this.log.showTimestamp} (来源: ${this.configSources['log.showTimestamp']})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "feishu-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Model Context Protocol server for Feishu integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",