溪午
拖了很久,也许是累了,脑中的弦紧绷着却一直陷入低精力,加上八月上中旬的关于dsh和dsv4pro0813的信息,一直分散着我的注意力。诚然,工具的迭代可以减轻人的负担,但是停滞不前只会让我成为工具的附庸。这个想法成为悬在头顶的一把达摩克利斯之剑,因而今日(0817)决定先补完上一阶段的复现总结,然后再一次开始学习。
闻钟
大概是七月十八日吧,我在某个赛事官方群里面摸鱼划水,偶然看见一个帖子,介绍的是自己的开源项目,于是我特地让DeepSeek替我粗略地分析了一下项目结构——其实简单得不能再简单了——README.md里面已经直接将整个项目结构和构建思路写得明明白白。
feishu_mcp 是一个简单的一套ngrok包装上七层安全鉴权的内网穿透工具,主要功能是通过白名单将有限的本地文件开放到公网上作为工作区给平台上的agent。听起来很复杂?是的,我第一次文件目录的时候深有同感。但是,当我粗浅地了解了一个ts项目构建的流程之后,遂始洞然。这里我粘贴一下该项目的主要结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| feishu_mcp/
├── src/
│ ├── index.ts # 入口:Express 服务 + MCP 路由(每请求身份上下文)
│ ├── auth/ # PIN / header / none 工具授权
│ ├── config.ts # 配置:环境变量集中管理 + SERVER_NAME/SERVER_VERSION 单一来源
│ ├── security/
│ │ ├── auth.ts # Bearer Token + 频率限制中间件
│ │ ├── requestContext.ts # AsyncLocalStorage 传递 token、用户与邮箱
│ │ ├── consent.ts # 绝对路径/敏感文件确认策略
│ │ ├── terminal.ts # 串行终端确认队列
│ │ ├── pathGuard.ts # 路径白名单 + 穿越防护
│ │ ├── fileGuard.ts # 文件类型黑名单 + 敏感文件过滤
│ │ ├── rateLimit.ts # 滑动窗口限流
│ │ ├── logger.ts # 操作审计日志(token 哈希存储)
│ │ └── trash.ts # 软删除回收站
│ └── tools/
│ ├── filesystem.ts # 9 个文件系统工具(注册入口)
│ ├── helpers.ts # 共享:resolveAndGuard / withToolHandler / 文本二 进制判定
│ └── atomicWrite.ts # 原子写:tmp + rename
├── scripts/
│ └── start-ngrok.ps1 # Windows 一键启动(服务 + 隧道)
├── docs/
│ └── aily-integration-guide.md # 飞书 Aily 接入指南
├── test/
│ ├── e2e_test.py # 端到端测试(37 项)
│ └── debug_mcp.py # 调试工具
├── .env.example # 环境变量模板
├── package.json
├── tsconfig.json
└── README.md
|
其中整个文件目录最核心的文件就是开头的 index.ts ,然后所有的文件依赖都是由 package.json 和 tsconfig.json 这两个程序生成的,更确切地说 package.json 生成了所有的包依赖, tsconfig.json 指定的是编译环境和编译器,一切复杂的文件结构都是附着于最开始的那个“骨架”。这的确是初学者从 typescript 入门 mcp 服务最小的原子组件了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function createReverseServer(){ const server = new McpServer({name:"demo",version:"1.0.0"});
server.registerTool( "reverse", { description: "将传入的字符串倒过来", inputSchema:{ text: z.string().describe("要翻转的字符串"), }, }, async(args: any) => ({ content:[ {type:"text" as const, text: args.text.split("").reverse().join("")}, ], }) ); return server; }
|
其中许多东西都是拖了很久才慢慢了解的——学习新东西就是比较让人困倦,怪自己太懈怠了… …
异步处理器:其中async属于异步函数,传入的参数 args 就是上文中传回的 text ,split(“”) 拆成字符数组 → reverse() 反转 → join(“”) 拼回字符串。一行完成字符串反转。
所以“type”行的意思是:返回一个对象字面量,其 text 字段存放的是——从参数对象 args 中取出 text 字符串属性,经拆分、反转、重组后得到的动态计算结果。
到这一步,已经可以完全看懂了工厂函数及其最小的实现的例子,然后整个mcp服务都是基于此构成的。了解完基本结构之后,我按照README.md文档的说明在本地打开端口监听
然后进行简单的第三方mcp服务的配置就可以正常使用了(有一说一,这类办公专精的平台第三方插件对coding类的支持稍显贫瘠)。
见鹿
我与我周旋久,宁作我。灯火阑珊,群星点盏,蝉鸣声嘶已将眠。