mcp-config-smoke-test.js 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. 'use strict';
  2. // Check the portable MCP contract without starting a server or printing credentials.
  3. const assert = require('node:assert/strict');
  4. const fs = require('node:fs');
  5. const path = require('node:path');
  6. const ROOT = path.resolve(__dirname, '..');
  7. function readJson(filePath) {
  8. return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''));
  9. }
  10. function isAbsoluteLike(value) {
  11. const text = String(value || '').trim();
  12. return path.isAbsolute(text) || /^[A-Za-z]:[\\/]/.test(text) || /^\\\\/.test(text);
  13. }
  14. function checkConfig(filePath, { packageConfig = false } = {}) {
  15. assert(fs.existsSync(filePath), `缺少 MCP 配置:${filePath}`);
  16. const raw = fs.readFileSync(filePath, 'utf8');
  17. assert(!/(?:r:|sk-(?!ant-))[A-Za-z0-9._~+/=-]{6,}/i.test(raw), `MCP 配置疑似包含凭据:${filePath}`);
  18. const config = readJson(filePath);
  19. const entry = config?.mcpServers?.['qiwei-assistant'];
  20. assert(entry && typeof entry === 'object', `${filePath} 未注册 qiwei-assistant`);
  21. assert(Array.isArray(entry.args), `${filePath} args 必须是数组`);
  22. assert(typeof entry.command === 'string' && entry.command.trim(), `${filePath} 缺少 command`);
  23. assert(typeof entry.cwd === 'string' && entry.cwd.trim(), `${filePath} 缺少 cwd`);
  24. assert(!isAbsoluteLike(entry.cwd), `${filePath} cwd 不能依赖绝对路径`);
  25. for (const arg of entry.args) assert(!isAbsoluteLike(arg), `${filePath} args 不能依赖绝对路径`);
  26. for (const [key, value] of Object.entries(entry.env || {})) {
  27. assert(typeof value === 'string', `${filePath} env.${key} 必须是字符串`);
  28. assert(!isAbsoluteLike(value), `${filePath} env.${key} 不能依赖绝对路径`);
  29. }
  30. if (packageConfig) {
  31. assert(entry.args.includes('mcp'), `${filePath} 未配置 mcp 子命令`);
  32. assert(/^\.([\\/])qiwei-workbench(?:\.exe)?$/i.test(entry.command), `${filePath} 未指向包内工作台`);
  33. assert(entry.cwd === '.', `${filePath} 交付包 cwd 必须为 .`);
  34. assert(entry.env?.QIWEI_PACKAGE_ROOT === '.', `${filePath} 缺少包根环境标记`);
  35. } else {
  36. assert(entry.command === 'node', `${filePath} 开发配置应使用 PATH 中的 node`);
  37. assert(entry.args.includes('./mcp/src/server.js'), `${filePath} 开发配置未指向源码入口`);
  38. }
  39. return {
  40. status: 'ok',
  41. file: path.relative(ROOT, filePath).replace(/\\/g, '/'),
  42. server: 'qiwei-assistant',
  43. command: entry.command,
  44. portable: true,
  45. };
  46. }
  47. function main() {
  48. const packageDir = process.argv[2] ? path.resolve(process.argv[2]) : '';
  49. const results = [checkConfig(path.join(ROOT, '.mcp.json'))];
  50. if (packageDir) results.push(checkConfig(path.join(packageDir, '.mcp.json'), { packageConfig: true }));
  51. process.stdout.write(`${JSON.stringify({ status: 'ok', configs: results }, null, 2)}\n`);
  52. }
  53. try {
  54. main();
  55. } catch (error) {
  56. process.stderr.write(`${error.message}\n`);
  57. process.exit(1);
  58. }