☰
AI Skill 数据获取实战:scripts、CLI、MCP 三种调用方式全解析
2026/10/3 15:32:00 网站建设 项目流程

1. 从"装完就吃灰"说起:Skill 为什么查不到数据

装完一个 AI Skill,兴冲冲地打开对话框问它"帮我查一下昨天的订单量",结果它回你一句"抱歉,我无法访问外部数据"。这个场景我见过太多次了,身边做 AI 应用的朋友、刚接触 Agent 开发的同事,几乎每个人都在这一步卡过。问题往往不在 Skill 本身写得烂,而在于调用接口这一层没打通——Skill 只是个"能力描述",它得通过某种通道去够到真实的数据源,通道没接上,再聪明的模型也只能干瞪眼。

这篇内容就是冲着这个痛点来的。我会把目前主流的三种 Skill 调用方式——scripts(脚本直调)、CLI(命令行接口)、MCP(模型上下文协议)——从原理到落地完整拆一遍,讲清楚它们各自适合什么场景、怎么配、坑在哪。不管你是刚上手 Agent Skill 的新手,还是已经写过几个 Skill 但总在数据获取上翻车的老手,都能从里面找到能直接抄的配置和排查思路。

先说结论性的判断,方便你对号入座:scripts 适合逻辑固定、一次性执行的任务;CLI 适合需要复用系统已有工具链、强调可组合性的场景;MCP 适合需要让模型动态发现和调用多个能力、且要求标准化交互的复杂 Agent。这三者不是替代关系,很多时候一个成熟的 Skill 会同时用到其中两种甚至三种。下面逐个展开。

2. scripts 方式:把数据获取逻辑写死在脚本里

2.1 scripts 的本质是"预编排"

scripts 方式的核心思路很朴素:Skill 被触发时,直接执行一段预先写好的脚本,脚本里包含了完整的数据获取逻辑——连哪个数据库、调哪个 API、怎么解析返回结果,全部硬编码在脚本里。模型本身不参与"怎么拿数据"的决策,它只负责触发和消费结果。

这种方式的优点是确定性极强。脚本跑起来,输入输出都是可预期的,不会因为模型"灵机一动"换了个查询方式就出岔子。对于数据源固定、查询逻辑不常变的场景,比如"每天定时拉取某张报表""根据用户 ID 查订单详情",scripts 是最省心的选择。

但它的短板也很明显:灵活性差。数据源换了、接口字段改了,你得回去改脚本重新部署。而且脚本里的逻辑对模型是不透明的,模型没法根据上下文动态调整查询策略。

2.2 一个能跑通的 scripts 结构长什么样

一个典型的 scripts 型 Skill 目录结构大概是这样:

my-skill/ ├── skill.json # Skill 元信息,声明触发条件和入口 ├── scripts/ │ └── fetch_data.py # 实际执行的数据获取脚本 └── requirements.txt # 依赖声明

skill.json里最关键的是入口声明,告诉运行时"触发这个 Skill 时去执行哪个脚本":

{ "name": "order-query", "description": "查询订单数据", "entry": "scripts/fetch_data.py", "runtime": "python3", "inputs": ["order_id"], "outputs": ["order_detail"] }

fetch_data.py里就是纯粹的数据逻辑,不掺任何模型调用:

import sys import json import requests def fetch_order(order_id): # 这里换成你真实的数据源地址 resp = requests.get( f"https://internal-api.example.com/orders/{order_id}", timeout=10 ) resp.raise_for_status() return resp.json() if __name__ == "__main__": order_id = sys.argv[1] result = fetch_order(order_id) # 输出必须是结构化 JSON,方便上层解析 print(json.dumps(result, ensure_ascii=False))

2.3 scripts 最容易踩的三个坑

第一个坑是输出格式不规范。很多人脚本里直接print一堆人类可读的文本,结果上层解析不了。记住:scripts 的输出必须是机器可解析的结构化数据,JSON 是通用选择。人类可读的格式化留给模型去做。

第二个坑是超时和重试没处理。脚本调外部接口,网络抖动是常态。我见过太多脚本因为没设timeout和重试逻辑,偶尔卡死导致整个 Skill 调用超时。建议至少加个三次重试加指数退避。

第三个坑是敏感信息硬编码。把 API Key、数据库密码直接写进脚本里,一旦 Skill 被分享出去就是灾难。正确做法是从环境变量或密钥管理服务读取:

import os API_KEY = os.environ.get("ORDER_API_KEY") if not API_KEY: raise RuntimeError("缺少 ORDER_API_KEY 环境变量")

提示:scripts 方式下,脚本的执行权限要严格控制。只给它完成任务所需的最小权限,别图省事用管理员账号跑。

3. CLI 方式:让 Skill 复用你已有的命令行工具链

3.1 CLI 和 scripts 的关键区别

乍一看 CLI 和 scripts 都是"执行一段程序",但它们的定位完全不同。scripts 是你为这个 Skill 专门写的数据获取逻辑;CLI 是系统里已经存在的命令行工具,Skill 只是去调用它。

这个区别带来的好处是巨大的:你不需要为每个 Skill 重新造轮子。系统里已经装好的git、curl、jq、各种数据库客户端、云服务 CLI,全都可以被 Skill 直接拿来用。Skill 的职责从"实现数据获取"退化成"编排已有工具",开发量骤降。

3.2 CLI 型 Skill 的典型编排模式

CLI 型 Skill 的核心工作是把自然语言意图翻译成命令行调用序列。举个实际例子,一个"查 GitLab 项目最近提交"的 Skill,底层就是组合几个 CLI 命令:

# 第一步:拿到项目 ID PROJECT_ID=$(gitlab-cli projects list --search "$PROJECT_NAME" --format json | jq -r '.[0].id') # 第二步:查最近提交 gitlab-cli commits list --project-id "$PROJECT_ID" --limit 10 --format json

Skill 的配置里声明它依赖哪些 CLI 工具,运行时检查这些工具是否可用:

{ "name": "gitlab-recent-commits", "description": "查询 GitLab 项目最近提交记录", "type": "cli", "requires": ["gitlab-cli", "jq"], "commands": [ "gitlab-cli projects list --search {project_name} --format json", "gitlab-cli commits list --project-id {project_id} --limit {limit} --format json" ] }

3.3 CLI 方式的选型判断:什么时候该用它

不是所有场景都适合 CLI。我的经验判断标准是三条:

  • 系统里已经有成熟的 CLI 工具能完成大部分工作,你只需要做编排。如果每个步骤都要自己写脚本,那还不如直接用 scripts。
  • 任务需要可组合、可复用。CLI 的管道哲学天然适合把多个小工具串起来,cmd1 | cmd2 | cmd3这种模式比写一个大脚本灵活得多。
  • 需要人工也能复现。CLI 命令是透明的,出问题时你可以直接在终端里手动跑一遍定位,而 scripts 里的逻辑往往藏在代码深处。

反过来,如果任务逻辑复杂、涉及大量条件分支和数据处理,CLI 的字符串拼接会变得非常脆弱,这时候 scripts 或 MCP 更合适。

3.4 CLI 调用中的参数注入与安全

CLI 方式最大的风险是命令注入。如果 Skill 把用户输入直接拼进命令行,恶意输入可能执行任意命令。比如用户输入; rm -rf /,拼出来的命令就完蛋了。

防护手段有两个层面。第一,永远用参数数组而不是字符串拼接:

import subprocess # 错误做法:字符串拼接 subprocess.run(f"gitlab-cli projects list --search {user_input}", shell=True) # 正确做法:参数数组,shell=False subprocess.run( ["gitlab-cli", "projects", "list", "--search", user_input], shell=False, timeout=30 )

第二,对输入做白名单校验。项目名、ID 这类参数往往有明确的格式,用正则卡一道,不合规的直接拒绝。

注意:CLI 型 Skill 依赖的外部工具版本要锁定。同一个命令在不同版本里参数可能不一样,今天能跑明天就报错,这种问题排查起来很折磨人。

4. MCP 方式:让模型动态发现和调用能力

4.1 MCP 到底解决了什么问题

前面两种方式有个共同的局限:能力是写死的。scripts 里写死了查什么,CLI 里写死了调哪些命令。模型在运行时不知道"我还能干别的什么",它只能按预设路径走。

MCP(Model Context Protocol,模型上下文协议)要解决的就是这个问题。它定义了一套标准协议,让模型能够在运行时动态发现有哪些能力可用、每个能力需要什么参数、返回什么结构。你可以把它理解成给模型配了一本"能力菜单",模型看着菜单点菜,而不是只能吃你提前做好的套餐。

MCP 的架构里有两个角色:MCP Server提供能力(暴露工具、资源、提示模板),MCP Client是模型侧的连接器,负责和 Server 通信、把能力列表喂给模型。通信可以走本地进程(stdio),也可以走网络(HTTP/SSE 等)。

4.2 一个最小可用的 MCP Server

用 Python 写一个 MCP Server 其实不复杂。下面这个例子暴露一个"查天气"的工具:

from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app = Server("weather-server") @app.list_tools() async def list_tools(): return [ Tool( name="get_weather", description="查询指定城市的当前天气", inputSchema={ "type": "object", "properties": { "city": {"type": "string", "description": "城市名称"} }, "required": ["city"] } ) ] @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_weather": city = arguments["city"] # 这里换成真实的数据获取逻辑 result = f"{city} 当前晴,气温 22 度" return [TextContent(type="text", text=result)] raise ValueError(f"未知工具: {name}") async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ == "__main__": import asyncio asyncio.run(main())

关键点在于list_tools返回的能力描述。模型拿到这份描述后,就知道自己可以调get_weather,并且知道要传city参数。这份描述写得好不好,直接决定模型能不能正确调用——描述含糊,模型就会乱传参数或者干脆不调。

4.3 MCP 的三种传输方式怎么选

MCP 支持多种传输方式,选错了会带来不必要的复杂度:

传输方式适用场景优点缺点
stdio本地进程、单机工具简单、无网络开销、安全只能本机、无法远程共享
HTTP/SSE远程服务、多客户端可远程、可共享需要处理网络和安全
WebSocket双向实时通信实时性好实现复杂、连接管理麻烦

我的建议是:能本地就本地(stdio),确实需要远程再上 HTTP。很多团队一上来就搞远程 MCP Server,结果被网络、鉴权、连接保活这些问题拖垮,其实他们的场景本地 stdio 完全够用。

4.4 MCP 工具描述怎么写才能让模型调对

这是 MCP 落地中最容易被低估的一环。工具描述不是写给人看的文档,是写给模型看的"使用说明书"。几个实操要点:

  • 描述里说清楚"什么时候用",而不只是"这是什么"。比如"当用户询问实时天气、气温、降水时使用此工具",比"查询天气"有效得多。
  • 参数描述要具体到格式。city参数如果只写"城市名称",模型可能传"北京"也可能传"北京市朝阳区",最好明确"传城市名,如'北京'、'上海'"。
  • 返回结构要稳定。模型会根据返回结构做后续推理,返回格式变来变去会让它无所适从。

提示:MCP Server 暴露的工具数量不宜过多。工具太多会让模型的选择成本上升,反而容易调错。按领域拆分多个 Server 比堆在一个里更好。

5. 三种方式横向对比:一张表看清选型逻辑

前面分别讲了三种方式,这里做个系统对比,帮你快速决策。

维度scriptsCLIMCP
能力发现静态,写死在配置里静态,依赖预装工具动态,运行时发现
开发成本中,每个 Skill 都要写低,复用已有工具高,要写 Server
灵活性低中高
确定性高中中
适合场景固定数据源、定时任务工具链编排、可复用流程复杂 Agent、多能力动态调用
主要风险输出格式、超时命令注入、版本漂移描述质量、连接稳定性
调试难度中低(可手动复现)高(涉及协议层)

选型的核心判断链条是这样的:先问"能力是否需要动态发现"——需要就上 MCP,不需要往下走;再问"系统里是否已有可用工具"——有就用 CLI 编排,没有就用 scripts 自己写。大部分简单场景,scripts 和 CLI 就能覆盖,别为了用 MCP 而用 MCP。

6. 混合使用:一个真实 Skill 的三层调用链

实际项目里,三种方式经常是混着用的。我拿一个"数据分析助手" Skill 举例,它的调用链是这样的:

第一层用 MCP 做能力发现。Skill 启动时连接一个 MCP Server,Server 暴露了"查数据库""生成图表""导出报表"三个工具。模型根据用户意图动态选择用哪个。

第二层用 CLI 做数据获取。当模型决定"查数据库"时,MCP Server 内部实际是调用系统的数据库 CLI 工具去执行查询,把结果结构化返回。

第三层用 scripts 做后处理。查询结果需要做复杂的清洗和聚合,这部分逻辑固定且复杂,封装成一个 Python 脚本,由 CLI 或 MCP Server 调用。

这种分层的好处是各司其职:MCP 负责"让模型知道能干什么",CLI 负责"用成熟工具干活",scripts 负责"处理固定复杂逻辑"。每一层都用它最擅长的方式,整体既灵活又可控。

配置上,MCP Server 的启动脚本里会声明它依赖哪些 CLI 和 scripts:

{ "mcpServers": { "data-assistant": { "command": "python3", "args": ["mcp_server.py"], "env": { "DB_CLI_PATH": "/usr/local/bin/db-cli", "SCRIPT_DIR": "./scripts" } } } }

7. 排查"查不到数据"的完整链路

回到开头那个问题:Skill 装好了但查不到数据。按下面的顺序排查,基本能定位到根因。

第一步,确认 Skill 是否真的被触发。很多"查不到数据"其实是 Skill 压根没被调用,模型走了通用回答路径。看运行日志里有没有 Skill 的触发记录,没有的话是触发条件配置的问题。

第二步,确认调用通道是否连通。scripts 方式看脚本能不能手动跑通;CLI 方式看命令在终端里能不能执行;MCP 方式看 Server 是否成功启动、Client 是否连上。这一步能把"环境问题"和"逻辑问题"分开。

第三步,确认数据源本身是否可达。通道通了但数据源连不上,是另一类问题。检查网络、鉴权、数据源地址。这一步经常被跳过,导致在错误的方向上排查半天。

第四步,确认输出格式是否被正确解析。数据拿到了但上层解析失败,表现也是"查不到数据"。把原始输出打出来看看,是不是 JSON 格式不对、字段名对不上。

第五步,确认权限是否足够。前面都通了但返回空结果,很可能是权限问题——账号能连上但看不到数据。这种问题最隐蔽,因为不报错,只是静默返回空。

我把常见现象和对应根因整理成表,方便对照:

现象可能根因排查动作
完全没有 Skill 调用记录触发条件未命中检查 skill.json 的触发配置
调用报错"命令不存在"CLI 工具未安装或路径不对手动执行命令验证
MCP 连接超时Server 未启动或传输配置错检查 Server 进程和传输方式
返回空结果但不报错权限不足或查询条件不匹配用相同条件手动查数据源
返回数据但模型说没有输出格式解析失败打印原始输出检查格式

8. 几个我踩过的坑和对应的经验

坑一:MCP 工具描述写得太"技术"。我一开始把工具描述写成 API 文档那种风格,参数类型、字段说明一应俱全,但模型就是调不对。后来改成"当用户想做什么时用这个工具"的自然语言描述,命中率立刻上来了。模型需要的是意图匹配线索,不是技术规格。

坑二:CLI 命令的退出码没检查。命令执行失败但退出码是 0 的情况太常见了,尤其是那些把错误信息打到 stdout 而不是 stderr 的工具。Skill 拿到"看似成功"的输出,解析出一堆垃圾。后来我养成了习惯:任何 CLI 调用后都检查退出码,并且对输出做格式校验,不合预期就当作失败处理。

坑三:scripts 的依赖没锁定版本。一个脚本依赖的库升级后行为变了,Skill 突然就不工作了。现在我的做法是每个 scripts 型 Skill 都带一个requirements.txt并锁定版本,部署时用虚拟环境隔离。

坑四:MCP Server 没做优雅关闭。进程被强杀时连接没释放,下次启动端口被占用。加上信号处理和资源清理逻辑后,这个问题就没了。

坑五:把敏感配置写进 Skill 配置里。分享 Skill 时忘了清理,密钥泄露。现在所有敏感配置一律走环境变量,Skill 配置里只留占位符。

9. 给不同阶段开发者的上手建议

如果你是刚接触 Skill 开发,从 scripts 开始。它最直观,逻辑全在你手里,出问题好定位。写两三个 scripts 型 Skill,把"触发—执行—返回"这个链路跑熟,再考虑其他方式。

如果你已经在用系统里的工具链,试试 CLI 方式。把你平时手动敲的命令封装成 Skill,会发现很多重复劳动可以自动化。重点练"自然语言意图到命令序列"的翻译能力。

如果你在做复杂的 Agent 应用,需要模型动态决策调用哪些能力,那 MCP 是绕不开的。但别一上来就搞远程 Server,先用 stdio 把本地链路跑通,把工具描述打磨好,再考虑扩展。

三种方式没有优劣之分,只有适不适合。我见过用 scripts 把简单任务做到极致的,也见过 MCP 用得很花哨但实际效果一般的。关键是匹配你的场景和团队能力,别被新概念牵着走。

最后分享一个我判断该用哪种方式的土办法:如果这个能力我能在终端里用几条命令搞定,就用 CLI;如果它需要一段固定逻辑,就用 scripts;如果模型需要自己决定"要不要用、用哪个",才上 MCP。这个判断标准不严谨,但实战中出奇地好用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询