news 2026/10/13 13:52:39

CDP实战:用Chrome DevTools Protocol驱动Electron桌面应用自动化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CDP实战:用Chrome DevTools Protocol驱动Electron桌面应用自动化

1. 为什么选CDP来操作Electron桌面应用

如果你做过Electron桌面应用的自动化,大概率经历过这样的窘境:界面稍微改一下,XPath就失效了;图片识别跑得慢吞吞,一遇到高分屏还认不准;用系统辅助功能接口遍历控件,Electron的非标准窗口又经常拿不到元素。

这些传统路径的痛点在Windows和macOS上我都踩过一遍,后来接入了CDP(Chrome DevTools Protocol)路线,整套方案才算是真正立住了。CDP本质上是Chromium内核暴露出来的一套调试协议,而Electron的渲染层本身就是Chromium,天然就能通过这个协议被外部程序驱动。可以说,只要你写在DevTools里能做的事,用CDP脚本全部都能做,而且速度、稳定性和可编程性都远超图像识别和坐标点击那一套。

这篇文章我会从协议机制讲起,然后直接给出一套可落地的启动、连接、操作流程,最后整理一份我在实战中踩过的坑和排查思路。目标是让读者拿到文章后,就知道怎么用CDP把自己的Electron应用自动化起来,而不是又停留在“听说过、不知道怎么做”的阶段。

这套方案适合谁?适合做桌面端自动化测试的程序员、做Agent桌面操作辅助的技术负责人、以及研究软件自动化控制方向的开发者。只要Electron应用能打开远程调试端口,剩下的都可以交给代码。

2. CDP工作原理解析

2.1 CDP的顶层结构与消息模型

CDP的全称是Chrome DevTools Protocol,从Chrome和Chromium内部演化出来的调试协议。它用起来长得非常像JSON-RPC:所有通信走WebSocket,消息由“方法调用”和“事件推送”组成。

整个协议按Domain(域)做划分,比如Page域管页面导航、Runtime域管JavaScript执行、DOM域管DOM树操作、Network域管网络请求、Input域管鼠标键盘仿真。每一个命令的格式长这样:

{"id": 1, "method": "Page.enable", "params": {}}

对应的应答也带同一个id:

{"id": 1, "result": {}}

事件则不带id,而是带method开头,例如:

{"method": "Page.loadEventFired", "params": {"timestamp": 123456.789}}

理解这个模型是后面编码的地基。Electron应用开启远程调试后,实际上会暴露出一个HTTP端点,先通过HTTP请求获取可连接的目标列表,再对选中的目标建立WebSocket连接,之后就能持续用命令和事件驱动它工作。

2.2 会话、目标与浏览器层级

Electron进程结构有点特殊:主进程负责窗口生命周期和系统集成,渲染进程负责界面。CDP其实分浏览器级和页面级两种target。浏览器级可以管理窗口、创建新页面、操作服务Worker等;页面级对应单个标签页或者单个渲染页面。

在Electron里做Agent操作,绝大多数情况只需要连接到页面级target即可。比较典型的一段工作流顺序是:

  • 以带调试端口的方式启动Electron应用;
  • 请求http://127.0.0.1:端口/json/list,拿到页面列表;
  • 选择想要的页面,建立WebSocket连接;
  • 发送CDP命令,实现点击、输入、数据读取;
  • 监听事件,获知页面加载、请求完成、控制台输出等状态。

一个容易混淆的点是:为什么不能用普通的HTTP接口直接执行JavaScript?因为CDP的核心通信面是WebSocket,HTTP端只用于发现目标和获取meta信息,真正干活的通道是WebSocket。后续代码也是围绕这条通道展开。

2.3 Electron对CDP支持的特殊之处

Electron和Chrome相比,开启CDP的方式不完全一样。Chrome在命令行加--remote-debugging-port=9222就行,Electron继承了Chromium的参数,通常还要随手加上用户数据目录参数,避免调试端口被已有实例占用。

另外,Electron应用自己也可以在主进程里调用webContents.debugger,以嵌入式方式直接使用CDP,不需要网络端口。对于无法改动代码的第三方Electron应用,我们只能走远程调试参数;对于自己团队维护的Electron应用,嵌入式方式更安全、更可控。

还有一个经验:Electron上app.commandLine.appendSwitch('remote-debugging-port', '9222')在ready事件前调用,可以透传给Chromium;但更稳妥的方式还是直接在系统命令行传参。因为改动代码本身就会牵涉构建和发布流程,能不加代码就不加代码。

明白了协议模型和连接路径,接下来就可以进入实操。

3. 实战准备:启动Electron并建立CDP连接通道

3.1 启动Electron并开启远程调试端口

假设你手头有一个打包好的Electron应用,可执行文件是MyApp.exe,或者是一个开发模式下的Electron项目,最简单的启动命令如下:

MyApp.exe --remote-debugging-port=9222 --user-data-dir=/tmp/myapp-profile

开发模式下也可以:

npx electron . --remote-debugging-port=9222 --user-data-dir=/tmp/myapp-profile

--user-data-dir其实就是应对“端口明明开了却连不上”这个问题。Electron会复用默认的用户数据目录,如果有旧进程或者残留锁,远程调试端口可能不生效。开一个独立目录,能避开90%的诡异问题。

如果你不希望每次都在命令行手输参数,Windows上可以写一个.bat脚本,macOS/Linux上可以写个shell别名,把端口、用户目录固定下来:

#!/bin/bash ./MyApp --remote-debugging-port=9333 --user-data-dir="$HOME/.auto-profile" &

这一步之后,打开浏览器访问http://127.0.0.1:9222/json/version,能看到包含Browser字符串的版本信息;再访问http://127.0.0.1:9222/json/list,能看到Electron当前打开的页面。能看到这两个JSON,说明CDP通道已经通了。

3.2 获取页面目标列表

我习惯先把目标列表打印出来,确认结构再写连接逻辑。用Node脚本非常直接:

const http = require('http'); http.get('http://127.0.0.1:9222/json/list', (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => { const targets = JSON.parse(data); targets.forEach((t) => console.log(t.type, t.title, t.webSocketDebuggerUrl)); }); });

返回的targets数组里,每个元素会包含id、title、url、webSocketDebuggerUrl等字段。webSocketDebuggerUrl就是我们后续要连的WebSocket地址。

还有一个细节值得提:/json/list只暴露当前可调试页面。若你的Electron应用是多窗口架构,每个窗口一般会对应一个页面target,不同窗口的title和url可以用来区分。

3.3 用Node建立WebSocket会话

远程调试协议本身不挑语言,只要你能写WebSocket和JSON,用什么语言都一样。我这里用Node加ws库,纯粹因为它跟Electron的JavaScript环境契合,后面解析数据也顺手。

npm install ws

连接代码骨架如下:

const WebSocket = require('ws'); const ws = new WebSocket('ws://127.0.0.1:9222/devtools/page/xxxx'); ws.on('open', () => { // 发送Enable类命令,订阅事件 ws.send(JSON.stringify({ id: 1, method: 'Page.enable', params: {} })); }); ws.on('message', (data) => { const msg = JSON.parse(data.toString()); if (msg.id) { // 处理命令响应 } else { // 处理事件回调 } });

建议给WebSocket封装一层command发送 + Promise返回的工具类,这样后续代码会清爽很多。核心逻辑就是:发送消息时带上递增的id,再把对应id的Promise存在Map里,收到响应后解析并resolve。

我这里给一个精简实现,可以直接照着用:

class CdpClient { constructor(url) { this.ws = new WebSocket(url); this.seq = 0; this.pending = new Map(); this.events = new Map(); this.ws.on('message', (data) => this.handleMessage(JSON.parse(data))); } open() { return new Promise((resolve, reject) => { this.ws.on('open', resolve); this.ws.on('error', reject); }); } send(method, params = {}) { const id = ++this.seq; this.ws.send(JSON.stringify({ id, method, params })); return new Promise((resolve, reject) => { this.pending.set(id, { resolve, reject }); }); } on(method, cb) { if (!this.events.has(method)) this.events.set(method, []); this.events.get(method).push(cb); } handleMessage(msg) { if (msg.id) { const p = this.pending.get(msg.id); if (!p) return; this.pending.delete(msg.id); if (msg.error) p.reject(new Error(msg.error.message)); else p.resolve(msg.result); } else if (msg.method) { const cbs = this.events.get(msg.method) || []; cbs.forEach((cb) => cb(msg.params)); } } }

这套封装是所有实操的基础。建立连接之后,一切操作都变成await client.send('Page.navigate', {url: '...'})这样直白的调用。

4. 核心实操:用CDP命令驱动Electron干活

4.1 页面导航与基础交互

打开一个页面地址,用Page.navigate:

await client.send('Page.navigate', { url: 'https://example.com' }); await waitForEvent(client, 'Page.loadEventFired');

但要注意,SPA应用内部跳转不一定触发loadEventFired,更可靠的判定是轮询执行一段JavaScript检查页面状态。我一般封装一个waitFor(conditionFunction):

async function waitFor(fn, timeout = 10000) { const start = Date.now(); while (Date.now() - start < timeout) { const result = await client.send('Runtime.evaluate', { expression: `(${fn.toString()})()`, returnByValue: true }); if (result.result && result.result.value) return true; await sleep(200); } throw new Error('waitFor timeout'); }

点击操作分两步:先用Runtime.evaluate找到元素并触发点击,或者用DOM域解析元素坐标后再用Input域鼠标操作。前一种简单,后一种更接近真实用户行为。

如果点击的是标准DOM按钮,直接使用JavaScript:

await client.send('Runtime.evaluate', { expression: `document.querySelector('button.login-btn').click()` });

如果元素被CSS覆盖、或者需要触发完整的鼠标事件链,那就用坐标点击。先用DOM.getDocument和DOM.querySelector拿到节点,再通过DOM.getBoxModel获取元素中心点,最后用Input.dispatchMouseEvent发送鼠标事件:

const doc = await client.send('DOM.getDocument'); const node = await client.send('DOM.querySelector', { nodeId: doc.root.nodeId, selector: '#submit-btn' }); const box = await client.send('DOM.getBoxModel', { nodeId: node.nodeId }); const [x, y] = getCenter(box.model); await client.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 }); await client.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 });

在Electron应用的自动化里,我更推荐优先尝试Runtime.evaluate,因为它依赖DOM结构而不是像素位置,UI样式调整时不会动不动就失效。只有当应用里用了canvas自绘、或者有WebGL场景时,才考虑走坐标事件。

4.2 文本输入与键盘事件

输入文字最直接的方式是设置input的value,然后派发事件。但很多现代前端框架监听的是input事件,只设value不派发事件,界面不会更新。稳妥做法是使用原生值设置器:

await client.send('Runtime.evaluate', { expression: ` (() => { const el = document.querySelector('#username'); const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set; setter.call(el, 'test_user'); el.dispatchEvent(new Event('input', { bubbles: true })); })() ` });

有的Electron应用里嵌入了CodeMirror、Monaco Editor这类富文本编辑器,直接操作input不行。这时候更好的路线是用Input.insertText:

await client.send('Input.insertText', { text: 'hello world' });

该方法先在系统层面把文本注入到焦点所在的编辑器里,能绕过React、Vue的受控组件,也能适配大多数自定义编辑器。

如果需要模拟真实键盘,比如按Tab跳转、按Enter提交:

await client.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13 }); await client.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13 });

一个踩过的坑:Electron菜单栏快捷键、系统快捷键会拦截部分按键事件。尤其在macOS上,如果全局快捷键映射和你的自动化按键冲突,要先把冲突的快捷键关掉,否则事件进不到渲染层。

4.3 数据读取与DOM查询

对Agent来说,读取页面信息是核心需求。最灵活的方式永远是Runtime.evaluate,因为可以直接在页面上下文里执行任意JavaScript,把结果序列化回来:

const result = await client.send('Runtime.evaluate', { expression: ` (() => { const items = [...document.querySelectorAll('.list-item')]; return items.map(el => ({ title: el.querySelector('.title')?.innerText, status: el.dataset.status })); })() `, returnByValue: true }); console.log(result.result.value);

returnByValue这个参数很关键。不设它的话,返回的是一个远程对象的引用,还得用Runtime.getProperties才能拿到值,多绕一层。设成true以后,基础类型和可序列化对象会直接通过JSON返回,极大简化数据提取。

如果你需要判断某个按钮是不是可见,也可以用类似方式:

await client.send('Runtime.evaluate', { expression: ` (() => { const el = document.querySelector('#modal'); const style = window.getComputedStyle(el); return style.display !== 'none' && style.visibility !== 'hidden' && el.offsetWidth > 0; })() `, returnByValue: true });

4.4 截图与页面状态留存

截图是自动化过程中非常实用的辅助功能,尤其是Agent执行关键操作前后的对比记录。CDP里页面级截图的命令是Page.captureScreenshot:

const shot = await client.send('Page.captureScreenshot', { format: 'png', captureBeyondViewport: false }); const buffer = Buffer.from(shot.data, 'base64'); fs.writeFileSync('screenshot.png', buffer);

captureBeyondViewport这个参数,如果你只想截当前可见窗口区域,就设false;想截整个长页面,就设true,并配一个合适的clip区域。

关于高清屏有一个经验:Electron在Windows上可能遇到DPI缩放导致截图模糊。解决方式是在截图前执行DeviceMetricsOverride,统一设置较高的设备像素比:

await client.send('Emulation.setDeviceMetricsOverride', { width: 1280, height: 720, deviceScaleFactor: 2, mobile: false });

这个操作会改变页面视口和设备像素比,截图清晰度会明显提升,但也要记得在截图后清理覆盖参数,避免影响后续点击坐标计算。

4.5 网络请求捕获与性能数据

Agent操作应用时,经常需要确认一个操作是否真的发起了后端请求,或者接口返回了什么。CDP的Network域可以监听全部网络活动。

首先启用网络事件:

await client.send('Network.enable'); client.on('Network.requestWillBeSent', (p) => { if (p.request.url.includes('/api/')) { console.log('REQUEST', p.request.method, p.request.url); } }); client.on('Network.responseReceived', (p) => { if (p.response.url.includes('/api/')) { console.log('RESPONSE', p.response.status, p.response.url); } });

要拿接口的响应体,需要配合Network.getResponseBody,但要拿到requestId。通常在responseReceived事件里就能取到p.requestId:

client.on('Network.responseReceived', async (p) => { if (!p.response.url.includes('/api/')) return; try { const body = await client.send('Network.getResponseBody', { requestId: p.requestId }); console.log(body.body); } catch (e) { // 有些响应体拿不到,比如被缓存或在Worker里 } });

有一点要提醒:Network.enable必须在请求发生前启用,否则早期事件不会回调到你的监听器。所以一般做法是启动连接后立刻开启Network域,再做页面操作。

如果你只需要关心性能数据,比如白屏时间、首屏加载耗时,可以启用Performance域:

await client.send('Performance.enable'); client.on('Performance.metrics', (p) => { console.log(p.metrics); });

4.6 控制台日志与异常捕获

桌面应用里经常有渲染进程的报错信息,平时肉眼很难捕捉。CDP的Runtime.consoleAPICalled事件能把console输出搬到你这边:

await client.send('Runtime.enable'); client.on('Runtime.consoleAPICalled', (p) => { console.log(`[console:${p.type}]`, p.args.map(formatRemoteValue)); }); client.on('Runtime.exceptionThrown', (p) => { console.error('EXCEPTION', p.exceptionDetails.text); });

Runtime.enable之后,页面里所有未捕获的异常和console输出都会被推送到客户端。对自动化Agent来说,这相当于给应用装了一个远程日志眼,调试问题会方便特别多。

有一点实际经验:某些Electron页面启用了contextIsolation,通过Runtime.evaluate执行的JS处于“主世界”,拿不到预加载脚本在“隔离世界”里注入的API。有这种限制时,要么找页面自己的调试入口,要么就在主世界里绕开对隔离API的依赖。

5. 常见问题与排查实录

5.1 端口开了但连接不上

最经典的一种情况是:应用已经带--remote-debugging-port启动,但浏览器访问http://127.0.0.1:9222/json/list一直失败。排查顺序我固定是这样:

  1. 确认进程真的带参数启动了,用任务管理器或ps aux | grep remote-debugging查看;
  2. 确认Electron没有复用旧实例。Electron的requestSingleInstanceLock会强行把新参数交给旧进程,导致你的调试参数根本没生效。解决方式就是加不同的--user-data-dir;
  3. 确认应用没有在ready之后才解析命令行参数。如果代码里手动初始化了app,参数传晚了同样不生效;
  4. Windows下检查防火墙,尤其是局域网环境。本地回环一般没问题,但有些安全软件会拦截本地端口监听;
  5. 确认端口没被占用,netstat -ano | findstr 9222看一下。

5.2 多窗口Electron应用,WebSocket连到了错误的页面

Electron菜单、弹窗、子窗口都会生成独立的target,易容错连到的是DevTools的调试页面而不是应用界面。我习惯连接前先打印所有target的type、title、url,再根据应用窗口标题做筛选。

不过Electron里WebContents的title可能和浏览器标签页不一样,更可靠的信号是url里带上file://或者应用自有协议前缀。再复杂点的话,也可以在主进程里对每个webContents调用webContents.debugger.attach,把调试端点精确绑定到目标WebContents上,完全绕开端口选择问题。

5.3 点击无反应,但JS执行不报错

一种常见原因是元素虽然存在,但被遮罩层盖住,比如Modal、Tooltip、Loading层。用DOM.getBoxModel算出的坐标点在遮罩上,点击事件被遮罩层吞了。排查时先执行:

const el = document.elementFromPoint(x, y);

看拿到的是不是目标元素。如果不是,就要先关闭遮罩层,或者改用Runtime.evaluate直接调用.click()。对Agent自动化而言,JavaScript触发点击在当前场景更显式,也不容易触发误点击。

5.4 CDP命令返回Target closed或Session closed

说明目标页面已经关掉了,可能你点了某个关闭按钮,或者应用整体退出了。捕获这个异常后,重连逻辑要去重新拉/json/list,并为新页面重新建立WebSocket连接。我在实际项目中写过一个重连模块,核心逻辑就是:记录当前页面的url或targetId,发现连接断开后轮询/json/list,找到对应页面再挂载同一套监听函数。这样应用重启后Agent还能自动衔接。

5.5 Electron安全策略导致页面无法注入

Electron应用如果启用了nodeIntegration: false、contextIsolation: true、沙箱开启,Runtime.evaluate只能运行在渲染进程的主世界,访问不到Node.js的require,也访问不到预加载脚本暴露的API。但页面DOM操作、网络请求监听、截图、Input事件都还是可以用的。

如果非要在渲染层拿更多能力,有两个思路:一是通过主进程侧CDP连接和webContents.executeJavaScriptInIsolatedWorld来注入到隔离世界,但这属于Electron调试API,不是纯CDP范畴;二是改造应用,在预加载脚本里挂一个调试辅助接口。对无法改代码的第三方Electron应用,主世界DOM操作基本够用,只是拿不到系统层能力而已。

5.6 事件订阅太多导致消息风暴

如果同时启用了Network.enable、Runtime.enable、Log.enable,页面活跃时每秒可能收到几十条事件。真实业务中我见过有人把事件日志全量打到控制台,结果自动化脚本被日志拖慢。合理做法是:只在需要的阶段启用对应Domain,处理完马上disable;或者在监听器里做关键词过滤。

另外,如果用的是我上面给的CdpClient封装,长时间运行后pendingMap可能因为某些响应永远不回来而堆积。建议在send方法加超时逻辑,超过指定时间就reject并删除map项。这一条在长时间自动化任务中非常关键。

5.7 截图内存暴涨

Electron应用本身内存占用不小,连续截图几百张时,如果每张都存base64字符串在内存里,很容易把Node进程内存撑爆。我一般截图后立刻转成Buffer写入磁盘,或者转成JPEG降低体积,不要长时间保留base64字符串。

Page.captureScreenshot还有一个参数fromSurface,有些Electron版本传true截的是整个窗口表面,传false截的是页面内容。遇到截出来的图是黑屏、或者出现系统标题栏时,试着调整这个参数。

6. 把CDP扩展成通用Agent操作框架的几点建议

如果你不只是想手动调试,而是想把它沉淀成一个多窗口、多步骤、可编排的Agent框架,我的经验是保留三层结构:

第一层是CDP连接层,负责建立WebSocket、维护消息id映射、统一处理错误和重连,对外只暴露send和on两个方法。

第二层是页面操作层,封装点击、输入、读取、等待、截图这些高频动作。这一层应该以“业务操作”命名,见了clickLoginButton、fillSearchBox,而不是一上来就是Runtime.evaluate。这样测试用例或者Agent脚本可读性会高很多。

第三层是流程编排层,负责把多个操作串成一个完整任务,比如登录、打开设置页、修改配置、保存、截图。这一层可以引入简单的超时控制、重试机制和步骤回滚。

再深化一步,Agent模块可以把页面操作抽象成“感知-决策-执行”的循环:感知阶段用CDP读取DOM、日志、网络请求;决策阶段对照用户意图生成操作序列;执行阶段调用CDP命令落地操作。因为CDP给的页面信息足够丰富,Agent能感知到的上下文比图像识别方案高一个量级,这也是我最终选定CDP路线的最重要理由。

关于安全边界,最后多说一句。远程调试端口一旦开启,任何能访问到该端口的本机进程都能控制你的应用。如果是在共享机器或自动化测试环境里跑,建议启动时生成随机端口,任务结束后关掉进程或删除调试端口参数,不要长期裸奔。这个细节看似不起眼,实际项目里遇到过二次开发的脚本误连目标端口捣乱的例子。

按这套方案跑下来,Electron桌面应用的自动化会变得和网页自动化一样顺手,甚至比网页自动化更可控,因为窗口本身就在你的掌控之下。以后再遇到Electron应用难自动化的问题,先开远程调试端口,再写CDP脚本,这条路基本不会让你失望。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 13:52:23

铅锤面内无控飞行导弹弹道仿真:Python实现与避坑指南

简介&#xff1a;本资源面向导弹弹道仿真初学者与工程技术人员&#xff0c;提供铅锤面内无控飞行导弹弹道的Python完整实现方案&#xff0c;帮助理解动力学方程数值求解与轨迹可视化流程。压缩包共7个文件&#xff0c;约556KB&#xff0c;包含3个py脚本、2个doc说明文档、1个xl…

作者头像 李华
网站建设 2026/10/13 13:50:49

AI编程基本功:需求拆解、代码审查与上下文管理实战

1. 被“提示词焦虑”带偏的这一年过去大半年&#xff0c;我身边做开发的朋友几乎都在焦虑同一件事&#xff1a;提示词写得不够好。各种“万能模板”“咒语大全”在群里疯传&#xff0c;好像只要背下几百条句式&#xff0c;就能让AI乖乖写出生产级代码。我自己也经历过这个阶段—…

作者头像 李华
网站建设 2026/10/13 13:49:49

GCC 9.5.0源码编译全指南:从依赖准备到版本切换的避坑手册

简介&#xff1a;GCC 9.5.0 官方源码压缩包&#xff0c;面向需要从底层构建、定制或二次开发编译器的开发者、系统管理员&#xff0c;以及系统学习编译原理的计算机专业学生。该资源采用 tar.gz 打包方式&#xff0c;压缩包整体大小约 122.2MB&#xff0c;根目录下仅含 gcc-9.5…

作者头像 李华
网站建设 2026/10/13 13:49:34

时序电路设计实验:从状态机到FPGA实测的完整闭环

简介&#xff1a;本资源为数字电路课程中「时序电路设计」的完整实验报告与配套工程文件&#xff0c;面向电子、通信、计算机类本科生及FPGA初学者&#xff0c;旨在帮助学习者系统掌握从状态机建模、Verilog/VHDL编码、Vivado综合实现到时序仿真验证的全流程实践能力。压缩包共…

作者头像 李华
网站建设 2026/10/13 13:49:27

知识工作插件矩阵:从采集到输出的一体化配置方案

我最近把自己电脑上那堆跟知识工作相关的插件认认真真收拾了一遍&#xff0c;整合成了一个仓库&#xff0c;名字就叫 knowledge-work-plugins。说白了&#xff0c;它不是一个一键安装的插件&#xff0c;而是一整套插件管理方案&#xff1a;所有与采集、管理、输出知识相关的浏览…

作者头像 李华
网站建设 2026/10/13 13:49:11

MCP协议封装低代码通讯录接口:从设计到Python落地实践

前阵子被派去做一个低代码平台的通讯录对接需求&#xff0c;客户要求把组织架构里的成员、部门、角色和外部联系人全部暴露给内部大模型助手调用。兜了一圈&#xff0c;决定用 MCP 把这套能力封装成标准接口&#xff0c;Python 写调用层&#xff0c;现在对接已经跑通&#xff0…

作者头像 李华