Tangc.pro’s blog

记录技术、生活与思考

给 MCP Server 加上企业级认证:一次 SSO 接入实践

背景与目标 我们团队维护一个内部数据服务,管实验数据的查询、文件和报告,最近在做的事情是把它暴露成 MCP Server,让 agent 直接查数据、下载文件、导出报告。 工具写完只是一个开始。这个服务里的数据涉及部门和委托公司,不是随便谁都能看的,所以认证和权限必须先解决。企业内部有现成的单点登录服务(下文简称 SSO),最终目标是:用户在 agent 平台里点一次登录,走 SSO 完成认证,之后 MCP 工具调用自动带上身份,数据权限对齐既有系统的语义。 这件事在 9 月于测试环境全链路跑通了。这篇文章记录协议原理、实现拆解和踩过的坑,协议部分和语言无关,换技术栈也能照着做。 主流架构:MCP 授权是什么设计 先说结论:MCP 的授权规范复用了 OAuth 2.0,把 MCP Server 定位成受保护资源(Resource Server),而不是授权服务器。你的 server 从头到尾不碰密码、不发 token、不管理会话,只做一件事:校验请求头里的 Bearer JWT。 我们这次的完整架构长这样: ┌─────────┐ 登录+授权(PKCE) ┌─────────┐ ①授权码流(OAuth客户端) ┌─────────────┐ │ Browser │ ───────────────> │ SSO │ <───────────────────── │ agent 平台 │ │ (用户) │ <─ 302 code 回调 │ (OIDC) │ ②JWT access token │ (连接服务) │ └─────────┘ (平台固定回调) └────┬────┘ ③token托管/自动续期 └──────┬──────┘ │ │ ⑤下发连接 │ ④ JWKS 公钥(server拉取缓存) ▼ ▼ ┌────────────────────────────────────────────────┐ │ MCP Server(任意技术栈) │ │ ⑥ 收 Bearer JWT → 本地验签 → 工具级数据权限 │ └────────────────────────────────────────────────┘ 流程分解一下: ...

September 24, 2026 · 后端开发 / AI · 3 min · 602 words · map[email:onetick@live.cn name:Hongyu]

别只盯着模型:聊聊 Agent Harness

从一个现象说起 用 AI 写代码的人大概都有过类似的体验:同一个模型,放在网页聊天框里改一个复杂项目,它给你贴一大段代码,粘哪儿你自己想;换到 Claude Code 这类编码工具里,它能自己读文件、跑测试、看报错、再来一轮,几分钟后交回来一个能跑的 diff。 模型明明是同一个,差别在哪? 在模型外面那一层:谁来维持任务状态、给它什么工具、允许它碰哪些文件、上下文快满了怎么办。这层东西有个名字,harness。 这个词原本是赛马用的马具,套在马身上,把马力变成人能驾驭的速度。放到 AI 语境里,它指包裹模型的整套工程脚手架。 harness 里都有什么 把一个 agent 产品拆开看,大致几层: 调度循环:最核心的部分。收任务、调模型、执行工具、看结果、决定下一步,直到任务完成或需要人介入。 工具层:文件读写、shell、搜索、浏览器。每个工具的定义、描述、错误信息都是设计出来的。 上下文管理:什么时候压缩历史、裁掉什么、哪些信息要挪到外部存储。 权限与沙箱:哪些操作直接放行、哪些要人确认、危险命令怎么拦。 记忆:跨会话记什么、忘什么。 骨架用伪代码写出来其实很短: while not task_done: response = model.call(context, tools) if response.wants_tool: result = sandbox.execute(response.tool_call) # 权限检查在这 context.append(result) else: break # 模型认为任务完成 跑得起来,demo 一小时就能写完。但生产级 harness 的复杂度全在省略号里:工具调用失败怎么重试、上下文超限怎么压缩、用户中途打断怎么恢复、两个工具并发改同一个文件怎么办。工程量和这个 while 循环完全不在一个量级。 判断一个 harness 的成色,我的经验是别看它顺利的时候多流畅,看它出错的时候怎么表现:重试有没有上限,错误信息有没有传回给模型,会不会带着坏状态继续跑。 五个常见误区 误区一:harness 就是个 API 封装 最低配确实如此,所以很多人低估了它。但"能跑"和"能用"之间隔着大量脏活:权限模型、上下文管理、失败恢复、可观测性。Anthropic 在 Building Effective Agents 里反复强调别急着上复杂框架,先把简单模式的可靠性做扎实。这句话反过来读,就是说简单模式做到可靠本身就很难。 误区二:上下文窗口够大,就不用管上下文了 窗口从 128K 涨到 200K 再到 1M,于是有人觉得历史记录一股脑塞进去就行。但窗口大不等于注意力够用。长上下文有个被反复观察到的现象(有人叫它 context rot):信息越多,模型对中段内容的利用越差,关键细节容易被淹没。 ...

July 16, 2026 · AI · 1 min · 140 words · map[email:onetick@live.cn name:Hongyu]

上海3岁男童被虐致死案:雪崩之下,没有一片雪花是无辜的

今天看了"女子虐死男友3岁儿子被判死缓"的新闻,痛惜中充满了愤怒。有了孩子后,对这类新闻越发敏感,情不自禁地代入其中——忍不住想,如果自己的孩子遭遇类似情景,该怎么办? 一个孩子,被碾碎的生命 2024年9月4日,一个叫黄梓澈的3岁男孩离世了。 他的名字里有个"澈"字,本该清澈、明亮。但他的人生,从一开始就被浑浊吞没。 法院查明的事实是这样的: 2024年1月,生父黄某以"奶奶病危"为由,从母亲马女士手中骗走了抚养权,把孩子交给了自己的同居女友——赵雨蝶,一个00后女孩。 此后七个月,孩子与母亲完全失联。 2024年7月至8月,赵雨蝶以孩子"顽皮不听话"为由,频繁殴打他的背部、臀部、腿部,甚至用牙咬。那些伤痕,一层叠着一层。 8月24日傍晚,上海浦东一处开放式公园里,因为孩子"乱跑",赵雨蝶用手拍打他的头面部,用树枝抽打他的身体,用脚踢踹他。最后,她把孩子从河道岸堤的斜坡拎起来,甩向堤面——孩子的侧身倒地,头部撞击地面。 当晚8点,孩子在家中昏迷。抢救无效,9月4日,脑死亡。 一个3岁的生命,就这样被碾碎了。 2026年4月21日,上海市第一中级人民法院宣判:赵雨蝶因虐待罪判处有期徒刑二年,因故意伤害罪判处死刑,缓期二年执行。死缓。 雪崩之下 雪崩之下,没有一片雪花是没有责任的。 这个案子中,任何一个环节尽职尽责,悲剧都不会发生。 施暴者赵雨蝶,毫无疑问是那块最沉重的冰。 她的残忍,把一个3岁的生命碾碎了。庭审中,她竟说是因为"忍不住"——忍不住?一个3岁的孩子,有什么值得你"忍不住"去打、去咬、去甩向地面? 但冰块坠落之前,每一层支撑早已松动。 生父黄某,用谎言把孩子从母亲身边夺走,用"工作忙"为自己开脱,用"拔管"企图掩盖真相。 他不是不知情,是选择不看见。 当邻居都能听到孩子说"我爱妈妈"时,同住一屋的他怎么可能毫不知觉?他不是旁观者,他是帮凶的沉默版本。 生母马女士,被剥夺了见孩子的权利,被以抚养权要挟。 但她是否穷尽了法律手段?在七个月的失联里,有没有报警?有没有申请强制探视?这道裂缝,她没能守住。 我不是在苛责一位母亲。在失去孩子的伤痛面前,任何追问都显得残忍。但追问本身,是为了让下一个母亲不再重复这样的命运。 邻居们听到了孩子的呼唤,看到了伤痕,却没有人拨出那个报警电话。 他们以为那是"别人家的孩子",以为自己不该"管闲事"。 社区、学校、医院——每一个本该看见异常的节点,都在这场雪崩中沉默了。 制度写在纸上,还没刻进社会的神经 我们刚刚建立了强制报告制度,刚刚配齐了62万名儿童主任,刚刚修订了《未成年人保护法》。 制度写在纸上,但还没有刻进社会的神经。 法律说"必须报告",但很多人心里还是"不该多事"。 这个孩子死于虐待,更死于一个社会的冷漠链条。 当每一个环节都选择"这不是我的责任",当每一次异常都被"多一事不如少一事"吞没,悲剧就不是偶然,而是必然。 另一场审判 法律已经给赵雨蝶判了死缓。 但这个社会还需要另一场审判——审判我们每一个人,在孩子伸出求救的手时,是选择握住,还是假装没看见。 制度可以修补,法律可以完善。 但真正能阻止下一场雪崩的,是每一个普通人的良知:当看见不对的事情时,不再告诉自己"这不关我"。 那个孩子反复说"我爱妈妈",其实是在说:请帮帮我。 下一次,愿有人听见。 愿每一个澈澈,都能被这个世界温柔以待。

April 21, 2026 · 随笔 · 1 min · 41 words · map[email:onetick@live.cn name:Hongyu]

测试:我的朋友姚远的快乐生活

人物介绍 姚远:某重点大学计算机系研究生,学霸一枚,成绩常年霸榜。外表斯文,戴着一副黑框眼镜。私下里却是个游戏狂魔,Warframe 和 DNF 双修玩家。对美女有着"学术般"的热情,但从不越界,止于欣赏。 第一幕:图书馆的清晨 场景:大学图书馆,早上七点。阳光透过落地窗洒进来。 姚远(低头奋笔疾书,桌上摊开三本厚厚的专业书): 今天的进度比预期快了二十分钟。很好。 (手机震动。姚远瞥了一眼,嘴角微微上扬。) 姚远: 又是做任务的好日子。Warframe 的警报倒计时还有…… (他迅速心算。) 四小时三十二分。够我把这篇论文的算法部分写完。 画外音: 这是姚远的一天。精确、高效,却也藏着别人看不见的小确幸。 第二幕:午间的"研究" 场景:校园食堂,人声鼎沸。 姚远端着餐盘,正在寻找座位。忽然,他的目光定格。 姚远(内心独白): 三点钟方向,长发,白裙子,气质型。两点钟方向,短发,运动风,活力型。 (他微微推了推眼镜,露出一个心满意足的微笑。) 姚远: 今日份的欣赏额度,达标。 (他找了个视野最好的位置坐下,一边吃饭一边继续"研究"。) 姚远(自言自语): 不是好色,是审美。这是对美的尊重。 画外音: 姚远的"好色"从不冒犯任何人。他只是喜欢欣赏美好事物,就像欣赏一道精妙的算法。 第三幕:深夜的游戏世界 场景:姚远的宿舍,晚上十一点。室友们已经睡了。 姚远(戴着耳机,屏幕上 Warframe 的战甲正在刷图): 这个配卡还是有优化空间……如果换成范围流,效率能提升 15%。 (突然,游戏里跳出一个交易请求。) 陌生玩家: 大佬,这个紫卡多少? 姚远: 看属性,这个值 200 白金。你急出的话,150 我收了。 陌生玩家: 成交!大佬爽快! 姚远(满意地笑了): 又是盆满钵满的一天。 (切屏,DNF 的登录界面出现。) 姚远: 接下来,该去阿拉德大陆转转了。 画外音: 没人知道,这个图书馆里沉默寡言的书呆子,是两个游戏世界里的"大佬"。他用实力证明:学霸也能把游戏玩到极致。 第四幕:友情的温暖 场景:次日下午,宿舍楼下。 我(看着姚远走过来): 姚远,听说你昨晚又熬夜打游戏了? 姚远: 凌晨一点就睡了,不算熬夜。而且…… (他从包里掏出一本笔记本递给我。) 姚远: 你上次问的那个算法优化问题,我帮你推了一下,笔记在最后一页。 我(翻开笔记本,里面密密麻麻全是推导过程): 这……你也太认真了吧! 姚远(推了推眼镜): 朋友嘛。而且帮你推导的过程,也巩固了我自己的理解。 我: 你这人,游戏玩得好,学习也好,还这么靠谱。你是怎么做到的? ...

March 23, 2026 · 随笔 · 1 min · 91 words · map[email:onetick@live.cn name:Hongyu]

对话:LLM 真的在「理解」吗?—— 揭开大语言模型的工作原理

编者按 这是一次关于大语言模型(LLM)本质的对话。从"相似性"这个直觉出发,聊到预训练、参数、注意力机制,最后聊到一个核心问题:LLM 真的在"理解"吗?有技术细节,也有对 AI 本质的冷静看法。 第一部分:预训练 —— 压缩互联网,不是背书 问:帮我理解 LLM 如何能准确生成正确的答案,它内部是怎么理解知识和上下文的?我之前了解过,似乎就是相似性,通过预训练投喂 LLM 模型。 答:你说的"相似性"是对的,但更准确说是高维空间中的位置关系。 预训练时,模型看了海量文本,但它不是在记忆,而是在压缩规律。想象你要总结一本小说——背下来每一句话是死记硬背,学会"主角遇到困难会怎么做"才是掌握规律。LLM 做的是后者。 它学会的是因果关系、语义关系(“国王 - 王后 ≈ 男 - 女”)、代码缩进规则、数学推理链条……这些规律被压缩进参数(权重)里。 每个词被转成一个向量(比如 12288 维),在这个空间里,“猫"和"狗"距离近,“猫"和"汽车"距离远。相似概念在空间中聚集,不同概念分散。模型通过位置关系"理解"语义。 第二部分:参数 —— 不是输入,是"大脑” 问:通常说的什么 xx B 参数,这个参数怎么理解,和程序开发中的方法参数类的有什么关系? 答:这两个"参数"完全不是一回事,只是中文翻译撞名了。 程序参数是输入,你调用时传进去的。而 LLM 的"参数"指的是模型内部的权重数量——训练完成后固定在那里的数值。 7B 模型 = 70 亿个权重 70B 模型 = 700 亿个权重 一个简化的例子: # 一个最简单的神经元 def neuron(x, w, b): # x 是输入,w 和 b 是"参数" return w * x + b LLM 有几十亿个这样的 w 和 b,层层连接形成网络。训练就是不断调整这些数,让输出越来越对。 类比一下:程序参数是你给函数的输入,LLM 参数是烘焙师傅 20 年积累的手感。你调用师傅(输入文本),他用手感(参数)做出蛋糕(输出)。 ...

March 3, 2026 · AI / 思考 · 2 min · 262 words · map[email:onetick@live.cn name:Hongyu]

Hugo 使用常识速查

从使用者角度整理的 Hugo 常识,够用就行。 目录结构 blog/ ├── hugo.toml # 主配置文件(也可用 config.toml/yaml) ├── content/ # 所有内容(文章、页面) │ ├── posts/ # 博客文章 │ └── about.md # 单页(/about/) ├── layouts/ # 自定义模板(覆盖主题) │ ├── _default/ │ │ ├── baseof.html # 基础模板 │ │ ├── single.html # 单页模板 │ │ └── list.html # 列表页模板 │ └── partials/ # 可复用片段 ├── themes/ # 主题目录 ├── assets/ # 需要处理的资源 │ └── css/extended/ # 自定义 CSS(自动合并) ├── static/ # 静态文件(直接复制) └── public/ # 构建输出(勿手动编辑) 构建原理 理解 Hugo 的构建流程,关键是看懂各目录的角色: ...

March 3, 2026 · 教程 / Hugo · 3 min · 514 words · map[email:onetick@live.cn name:Hongyu]

fail2ban 基本介绍与使用指南

什么是 fail2ban? 简单来说,fail2ban 就是一个自动封禁 IP 的工具。 当你运行 SSH、Web 服务或者其他网络服务时,总会有人试图暴力破解或者恶意扫描。fail2ban 会监控日志文件,当发现某个 IP 在短时间内多次失败尝试后,自动调用防火墙把这个 IP 封禁一段时间。 为什么需要它? 我当初上云服务器的第一件事就是装 fail2ban。原因很简单: 减少日志噪音,不用看着满屏的失败登录记录 自动阻止暴力破解 配置好后基本不用管 可以针对不同服务设置不同规则 安装 Ubuntu/Debian sudo apt update sudo apt install fail2ban CentOS/RHEL sudo yum install epel-release sudo yum install fail2ban 安装完成后,服务会自动启动: sudo systemctl enable fail2ban sudo systemctl start fail2ban 基本配置 fail2ban 的配置文件在 /etc/fail2ban/ 目录下: fail2ban.conf - 主配置(一般不用改) jail.conf - 默认规则配置(不要直接修改) jail.local - 本地覆盖配置(在这里自定义) 创建本地配置 sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local 最小可用配置 编辑 /etc/fail2ban/jail.local: ...

March 2, 2026 · 运维 / 安全 · 3 min · 461 words · map[email:onetick@live.cn name:Hongyu]

Hello World

欢迎来到我的博客 🎉 这是我的第一篇博客文章,使用 Hugo + PaperMod 主题搭建。 写博客这件事 几年前我也搭建过个人博客。那时写文章流程很完整:定主题、列大纲、理结构、写正文、反复修改润色,最后才发布。认真写了小半年,后来工作生活繁忙,慢慢就搁置了。再后来云服务器到期被收回,文章又没做版本管理,就这么找不到了。 有时真感慨岁月不饶人。这几年起起伏伏,我也经历了多个身份的转变——如今是一位丈夫、一位父亲,年龄也到了 30+。心态与之前有了很大不同,很多个瞬间让我感觉人生到了一个新阶段,提醒我要做出些改变。 前些日子翻看工作生活笔记,看到久远的一些文章草稿,又萌发了自己做博客网站的念头:在写作中沉淀自己,顺着过程中涌现的灵感做些新尝试,保持生活的热情和创造力。 LLM 最近几年很热门,涌现了很多基于其的应用和工具——ChatGPT、Claude、OpenClaw……这次博客我也想尝试些新方式:技术类文章,我会指定主题、大纲、结构和写作偏好,内容通过 prompt、RAG 之类的方式由 LLM 生成,来源是我自己的笔记和相关技术文档,最终生成的文章我会仔细检查修改后发布。看看能不能碰撞出什么火花。 关于这个博客 📝 使用 Markdown 写作 🚀 基于 Hugo 静态生成 🎨 PaperMod 主题 📦 Nginx 反向代理 接下来 在这里我会记录: 技术学习笔记 生活感悟 有趣的项目 感谢访问!

March 2, 2026 · 随笔 · 1 min · 39 words · map[email:onetick@live.cn name:Hongyu]

踩坑实录:ONLYOFFICE forcesave 的同步化处理

背景 最近在做一个在线文档协作系统,用户可以在平台上编辑文档,编辑完成后触发一系列业务流程——生成版本、数字签名、归档等。文档编辑器选用了 ONLYOFFICE,功能强大,协作体验也好。 看起来是个挺常规的需求,对吧? 我当初也是这么想的。直到系统上线后,用户反馈"数字签名偶尔会失效",我才意识到自己踩进了一个不大不小的坑里。 问题出在哪? ONLYOFFICE 的架构中,文档编辑和文件存储是分离的。用户在编辑器里修改文档,改动先停留在 ONLYOFFICE Document Server 的内存中,并不会立即写入你的存储后端。 ONLYOFFICE 提供了 forcesave 接口,可以强制保存文档。我最初的实现是这样的: // 用户点击"提交"按钮时触发 public void submitDocument(String documentId) { // 1. 调用 forcesave onlyofficeService.forceSave(documentId); // 2. 获取文件内容 byte[] fileContent = storageService.getFile(documentId); // 3. 生成数字签名 String signature = signService.sign(fileContent); // 4. 保存签名记录 recordService.saveSignature(documentId, signature); } 看起来逻辑清晰,没什么问题。但上线后,用户反馈:某些文档的数字签名校验失败。 排查后发现:当用户打开文档验证时,文件内容和签名时不一致——明明签名后没人再编辑过,怎么文件变了? 根本原因:forcesave 是异步的 翻了 ONLYOFFICE 的文档,才发现 forcesave 的调用流程是这样的: 后端调用 forcesave API ↓ ONLYOFFICE Document Server 接收请求 ↓ Document Server 在内部调度保存任务(异步) ↓ 保存完成后,Document Server 回调后端的 Callback Handler ↓ 后端在回调中收到最新的文件 URL 也就是说,forcesave() 返回时,文件还没保存完成。真正的文件更新是在回调里发生的。 ...

November 15, 2025 · 后端开发 / 分布式 · 3 min · 631 words · map[email:onetick@live.cn name:Hongyu]

当 if-else 长成一棵树:用策略模式重构 OnlyOffice 回调处理

问题从哪里开始 项目里集成了 OnlyOffice 文档编辑器,功能挺好用,但回调处理这块慢慢变成了"噩梦"。 OnlyOffice 会推送各种回调,比如用户保存文档、强制保存、文档关闭等等。每种回调有不同的 status 字段,对应不同的处理逻辑。一开始代码写得很"朴素": // FileServiceImpl.java - 曾经的模样 public void onlyOfficeCallback(OnlyOfficeCallbackDTO callbackDTO, File file) { if (callbackDTO.getStatus() == 2) { if (callbackDTO.getActions() != null && callbackDTO.getActions().stream().anyMatch(a -> a.getType() == 0)) { // 保存逻辑:下载文档、更新文件... // 几十行代码 } } else if (callbackDTO.getStatus() == 6) { // 强制保存逻辑 // 又是几十行代码 } else if (callbackDTO.getStatus() == 1) { // 正在编辑... } else if (...) { // 更多分支... } } 然后还有 Command 调用——主动向 OnlyOffice 发命令的逻辑,也堆在同一个 Service 里: public void sendCommand(String command, Map<String, Object> params) { if ("forcesave".equals(command)) { // 强制保存命令逻辑 } else if ("info".equals(command)) { // 获取信息命令逻辑 } // 更多分支... } 问题来了: ...

November 13, 2025 · 后端开发 / 设计模式 · 3 min · 559 words · map[email:onetick@live.cn name:Hongyu]