feishu-mcp
Version:
Model Context Protocol server for Feishu integration
103 lines (102 loc) • 4.52 kB
JavaScript
import { resolve } from 'path';
import { homedir } from 'os';
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
export const GLOBAL_CONFIG_DIR = resolve(homedir(), '.cache', 'feishu-mcp');
export const GLOBAL_CONFIG_FILE = resolve(GLOBAL_CONFIG_DIR, '.env');
const CONFIG_KEYS = {
FEISHU_APP_ID: '飞书应用 App ID',
FEISHU_APP_SECRET: '飞书应用 App Secret',
FEISHU_AUTH_TYPE: '认证类型:tenant(应用身份)或 user(用户身份,支持 task/member)',
FEISHU_ENABLED_MODULES: '启用的功能模块,逗号分隔,可选值: document,task,member,calendar,all',
FEISHU_BASE_URL: '飞书 API 基础地址,默认 https://open.feishu.cn/open-apis',
FEISHU_SCOPE_VALIDATION: '是否启用权限校验:true 或 false,默认 true',
PORT: '服务监听端口,默认 3333',
};
/** 读取并解析 .env 文件为 key-value 对象 */
export function readEnvFile(filePath) {
if (!existsSync(filePath))
return {};
const result = {};
for (const line of readFileSync(filePath, 'utf-8').split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#'))
continue;
const idx = trimmed.indexOf('=');
if (idx === -1)
continue;
result[trimmed.slice(0, idx).trim()] = trimmed.slice(idx + 1).trim();
}
return result;
}
/** 向 .env 文件写入或更新一个 key */
export function writeEnvKey(filePath, key, value) {
mkdirSync(resolve(filePath, '..'), { recursive: true });
let content = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
const regex = new RegExp(`^${key}=.*$`, 'm');
if (regex.test(content)) {
content = content.replace(regex, `${key}=${value}`);
}
else {
content += (content && !content.endsWith('\n') ? '\n' : '') + `${key}=${value}\n`;
}
writeFileSync(filePath, content, 'utf-8');
}
/** 对敏感字段做脱敏处理 */
function maskValue(key, value) {
if (key.includes('SECRET') || key.includes('APP_ID')) {
if (value.length <= 6)
return '****';
return `${value.slice(0, 3)}****${value.slice(-3)}`;
}
return value;
}
/** 展示当前生效配置 */
export function handleConfigShow(envPath) {
// 仅当加载来源与全局配置文件不同时,才额外展示全局配置文件内容(避免重复)
const showGlobal = envPath !== GLOBAL_CONFIG_FILE;
const output = {
configFile: existsSync(GLOBAL_CONFIG_FILE) ? GLOBAL_CONFIG_FILE : null,
loadedFrom: envPath ?? null,
config: {
FEISHU_APP_ID: process.env.FEISHU_APP_ID
? maskValue('FEISHU_APP_ID', process.env.FEISHU_APP_ID) : '(未设置)',
FEISHU_APP_SECRET: process.env.FEISHU_APP_SECRET
? maskValue('FEISHU_APP_SECRET', process.env.FEISHU_APP_SECRET) : '(未设置)',
FEISHU_AUTH_TYPE: process.env.FEISHU_AUTH_TYPE ?? 'tenant (默认)',
FEISHU_ENABLED_MODULES: process.env.FEISHU_ENABLED_MODULES ?? 'document (默认)',
PORT: process.env.PORT ?? '3333 (默认)',
},
};
if (showGlobal) {
const fileConfig = readEnvFile(GLOBAL_CONFIG_FILE);
output.globalConfigFile = Object.keys(fileConfig).length
? Object.fromEntries(Object.entries(fileConfig).map(([k, v]) => [k, maskValue(k, v)]))
: '(文件不存在)';
}
process.stdout.write(JSON.stringify(output, null, 2) + '\n');
}
/** 向配置文件写入 key=value,写入目标与当前加载来源保持一致 */
export function handleConfigSet(key, value, envPath) {
if (!key || !value) {
process.stdout.write(JSON.stringify({
usage: 'feishu-tool config set <KEY> <VALUE>',
availableKeys: CONFIG_KEYS,
}, null, 2) + '\n');
process.exit(0);
}
if (!(key in CONFIG_KEYS)) {
process.stdout.write(JSON.stringify({
error: `未知配置项: ${key}`,
availableKeys: CONFIG_KEYS,
}) + '\n');
process.exit(1);
}
// 写入目标与读取来源一致:已有加载文件则写入该文件,否则写入全局配置文件
const targetFile = envPath ?? GLOBAL_CONFIG_FILE;
writeEnvKey(targetFile, key, value);
process.stdout.write(JSON.stringify({
ok: true,
file: targetFile,
set: { [key]: key.includes('SECRET') || key.includes('APP_ID') ? maskValue(key, value) : value },
}) + '\n');
}