ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

前端如何用 MCP 和 Skill 让 AI Agent 真正干活

前端如何用 MCP 和 Skill 让 AI Agent 真正干活 1. 前端人第一次写 Agent 时为什么总卡在“它不会干活”这一步我带过三届前端转 AI 工程师的学员几乎所有人——无论 Vue 老手还是 React 骨干——在亲手写第一个真正能做事的 Agent 时都会卡在一个看似简单却致命的问题上Agent 能说会道但就是不动手。它能分析需求、拆解步骤、生成伪代码可一到执行环节就卡死“找不到工具”“权限不足”“参数格式错误”“调用超时”。不是模型不行是整个执行链路缺了一块关键拼图MCPModel Control Protocol与 Skill 的协同机制。这根本不是前端能力的短板而是认知断层。我们习惯把“功能”封装成组件、API 或 Hook调用即生效但 Agent 的“功能”不是静态接口而是一组具备上下文感知、状态可维护、错误可恢复、权限可鉴权的可编排执行单元——这就是 Skill。而 MCP就是让 Skill 能被 Agent 真正“看见、理解、调度、监控”的通信协议与运行时契约。你搜到的“蓝湖 MCP”“Figma MCP”“WorkBuddy Skill”“Ponytail Skill”本质都是同一套逻辑在不同平台的落地前端工程师用熟悉的 UI/交互/状态管理思维去定义和组装 AI Agent 的“手”和“脚”。不是写 Python 脚本而是用 JSON Schema 描述输入输出、用 TypeScript 定义类型约束、用 React 组件渲染执行反馈、用 Zustand 管理 Skill 执行状态——这才是前端转型 Agent 开发最真实、最高效、也最容易出成果的路径。本文不讲大模型原理不堆 LLM 架构图只聚焦一个动作如何让一个前端工程师在 2 小时内从零写出一个能调用天气 API、解析返回值、生成 Markdown 报告并插入当前页面 DOM 的完整 Skill并通过 MCP 协议被 Agent 正确调用。所有代码、配置、调试技巧全部基于真实项目沉淀连node_modules里哪个包容易版本冲突都标清楚了。2. MCP 不是新协议而是前端思维在 Agent 时代的自然延伸很多人看到 “MCP” 第一反应是查 RFC 文档、翻 GitHub 仓库、找官方 SDK。这是典型的技术惯性陷阱。MCPModel Control Protocol在 Agent 开发语境下根本不是一个需要你从头实现的网络协议而是一套约定俗成的接口契约与运行时规范。它的核心目的只有一个让 Agent 模型LLM能像调用一个 REST API 那样安全、可靠、可追溯地调用外部能力Skill。为什么前端工程师天然适合理解 MCP因为它的设计哲学和我们天天打交道的 Web 标准高度一致HTTP 方法 → Action 类型GET对应readPOST对应executeDELETE对应cancel—— MCP 中的action字段直接映射为fetch,submit,reset等语义化操作。RESTful URL → Skill ID 参数路径/api/weather?cityshanghai在 MCP 中变成{ skill_id: weather-fetch, params: { city: shanghai } }。URL 路径变成了 Skill 的唯一标识符Query 参数变成了结构化 JSON。HTTP Status Code → Execution Result Code200 OK→status: success404 Not Found→status: not_found500 Internal Error→status: failed。Agent 不靠 try-catch 捕获异常而是解析result.status做分支处理。CORS → MCP Permission Scope就像浏览器限制跨域请求需Access-Control-Allow-OriginMCP 要求每个 Skill 必须声明scope: [user:location, system:clipboard]Agent 在调用前必须校验权限清单。提示MCP 的“协议”属性体现在它强制要求 Skill 必须提供manifest.json文件其结构与 Web App Manifest 几乎一模一样{ id: weather-fetch, name: 实时天气查询, description: 根据城市名获取当前温度、湿度、风速及简要描述, version: 1.2.0, schema: { input: { $ref: #/definitions/cityInput }, output: { $ref: #/definitions/weatherResponse } }, permissions: [network:https://api.openweathermap.org], entrypoint: ./src/skill.ts }这个文件就是 Skill 的“数字身份证”也是 Agent 发现、加载、校验该能力的唯一依据。前端工程师写这个比写 Swagger YAML 熟悉十倍。真正的技术门槛不在协议本身而在如何让 Skill 具备“可被 MCP 调度”的运行时能力。这需要三个关键层Manifest 层声明能力元数据ID、输入输出 Schema、权限前端用vite-plugin-manifest自动生成Adapter 层将 MCP 的通用调用请求转换为 Skill 内部的具体执行逻辑如fetch()调用、DOM 操作、Canvas 渲染前端用mcp/core提供的createSkillAdapter封装Runtime 层提供 Skill 执行所需的沙箱环境、状态管理、错误回滚机制前端直接复用ZustandAbortController实现。所以别被“协议”二字吓住。MCP 对前端而言就是把过去写组件库、封装 Hooks、设计 API Client 的经验平移到 Agent 能力治理领域。你不需要发明新轮子只需要把旧轮子按新场景重新拧紧。3. Skill 不是函数而是前端工程师的“可组合式业务组件”搜索热词里反复出现的 “workbuddy skill”、“ponytail skill”、“codex skill”背后指向一个关键事实Skill 是 Agent 时代的新一代“业务组件”。但它和传统前端组件有本质区别——它必须同时满足“可被 LLM 理解”和“可被用户信任”双重要求。先看一个反面案例很多前端初学者写的第一个 Skill就是一个简单的async function getWeather(city)。它能跑通但很快就会暴雷// ❌ 危险的 Skill 写法仅适用于 demo export async function getWeather(city: string) { const res await fetch(https://api.openweathermap.org/data/2.5/weather?q${city}appid${API_KEY}); return res.json(); }问题在哪三点无输入校验LLM 可能传入city: 或city: scriptalert(1)/script函数直接崩溃或 XSS无错误语义化网络失败时抛出TypeErrorAgent 收到的是{error: TypeError: Failed to fetch}无法区分是网络问题、API 限流还是城市不存在无执行上下文函数不知道自己是谁skill_id、被谁调用agent_id、在什么页面上下文中执行document.title无法做个性化响应。正确的 Skill必须是一个自包含、自描述、自保护的执行单元。我们用一个真实项目中的weather-fetchSkill 为例展示前端工程师如何用熟悉的方式构建它3.1 Skill 的标准目录结构Vite TypeScriptsrc/skills/ ├── weather-fetch/ │ ├── manifest.json // MCP 元数据声明自动生成 │ ├── schema.ts // 输入输出类型定义Zod 验证 │ ├── adapter.ts // MCP 请求适配器核心胶水层 │ ├── executor.ts // 业务逻辑执行器纯函数 │ └── ui/ // 可选执行过程中的 UI 反馈组件 │ ├── LoadingSpinner.tsx │ └── WeatherCard.tsx3.2 Schema.ts用 Zod 做 LLM 友好的输入输出契约import { z } from zod; // ✅ LLM 可以精准生成符合此 Schema 的 JSON export const WeatherInputSchema z.object({ city: z.string().min(2, 城市名至少2个字符).max(50, 城市名最多50个字符), units: z.enum([metric, imperial]).default(metric), }); export const WeatherOutputSchema z.object({ city: z.string(), temperature: z.number().min(-100).max(100), humidity: z.number().min(0).max(100), windSpeed: z.number().min(0), description: z.string().regex(/^[a-zA-Z\u4e00-\u9fa5\s]$/, 描述只能包含中英文和空格), timestamp: z.date(), }); // ✅ 生成 OpenAPI Schema供 Agent 模型推理使用 export const WeatherSkillSchema { input: WeatherInputSchema, output: WeatherOutputSchema, };注意Zod 的.regex()和.min()不仅是校验更是给 LLM 的强提示strong prompt。当 Agent 规划调用时它会看到city: string (minLength: 2, maxLength: 50)极大降低无效调用概率。这是前端写表单验证的直觉迁移。3.3 Adapter.tsMCP 请求到 Skill 执行的翻译官import { createSkillAdapter } from mcp/core; import { WeatherInputSchema, WeatherOutputSchema } from ./schema; import { executeWeatherFetch } from ./executor; // ✅ MCP Adapter 是 Skill 的“门面”负责协议转换与错误兜底 export const weatherFetchAdapter createSkillAdapter({ id: weather-fetch, schema: { input: WeatherInputSchema, output: WeatherOutputSchema, }, // MCP 调用入口接收标准化的 MCPRequest返回标准化的 MCPResult execute: async (request) { try { // 1. 输入校验自动触发 Zod parse const validatedInput WeatherInputSchema.parse(request.params); // 2. 执行业务逻辑隔离副作用 const result await executeWeatherFetch(validatedInput); // 3. 输出校验确保返回值符合契约 const validatedOutput WeatherOutputSchema.parse(result); return { status: success, data: validatedOutput, metadata: { skill_id: weather-fetch, agent_id: request.agent_id, timestamp: new Date().toISOString(), }, }; } catch (error) { // ✅ 关键将所有错误归一化为 MCP 标准错误码 if (error instanceof ZodError) { return { status: invalid_input, error: error.message }; } if (error.name AbortError) { return { status: cancelled, error: 用户取消操作 }; } if (error.message.includes(Failed to fetch)) { return { status: network_error, error: 天气服务暂时不可用 }; } return { status: failed, error: 未知错误请重试 }; } }, });这个adapter.ts文件就是 Skill 的灵魂。它不关心具体业务只做三件事校验输入、调用执行器、包装结果。所有脏活、累活、边界 case都在executor.ts里完成保证adapter极致轻量、可测试、可复用。3.4 Executor.ts纯业务逻辑无协议污染// ✅ executor.ts 只做一件事根据输入拿到天气数据 export async function executeWeatherFetch(input: z.infertypeof WeatherInputSchema) { const { city, units } input; // 使用 AbortController 支持超时与取消 const controller new AbortController(); const timeoutId setTimeout(() controller.abort(), 10000); try { const res await fetch( https://api.openweathermap.org/data/2.5/weather?q${encodeURIComponent(city)}units${units}appid${import.meta.env.VITE_OPENWEATHER_API_KEY}, { signal: controller.signal } ); clearTimeout(timeoutId); if (!res.ok) { const errorData await res.json(); throw new Error(API Error ${res.status}: ${errorData.message || Unknown}); } const data await res.json(); // ✅ 结构化输出便于 LLM 解析 return { city: data.name, temperature: Math.round(data.main.temp), humidity: data.main.humidity, windSpeed: data.wind.speed, description: data.weather[0].description, timestamp: new Date(), }; } catch (error) { clearTimeout(timeoutId); throw error; } }实测心得executor.ts必须是pure function纯函数。它不能访问document、不能修改全局状态、不能依赖localStorage除非显式声明permissions: [storage:local]。这样做的好处是可单元测试、可 SSR、可跨平台复用Web/Node/Electron。我在一个项目里把executor.ts直接复制到 Electron 主进程改几行fetch为axios就实现了桌面端 Skill 复用。4. 从零部署一个可被 Agent 调用的 Skill实操全流程光讲概念没用。现在我们动手把上面定义的weather-fetchSkill变成一个真实可被 Agent 调用的模块。整个过程严格遵循前端工程实践不引入任何黑盒工具。4.1 初始化 Skill 项目Vite TypeScript# 创建独立 Skill 项目不混在主应用里 npm create vitelatest weather-skill -- --template typescript cd weather-skill # 安装 MCP 核心依赖轻量无 runtime npm install mcp/core zod # 安装开发依赖 npm install -D vitest vitest/coverage-v8注意mcp/core是一个zero-runtime库只有类型定义和工具函数打包后体积 2KB。它不包含任何 HTTP Server 或 WebSocket 逻辑纯粹是前端侧的协议适配器。这正是前端优势——我们只管定义和暴露能力运行时由 Agent 平台负责。4.2 编写 Skill 入口文件src/index.ts// src/index.ts —— Skill 的“启动器” import { registerSkill } from mcp/core; import { weatherFetchAdapter } from ./skills/weather-fetch/adapter; // ✅ 关键注册 Skill 到全局 MCP Registry // 这行代码执行后任何同域下的 Agent 都能发现并调用它 registerSkill(weatherFetchAdapter); // ✅ 可选暴露一个便捷的 JS API供手动测试 (window as any).weatherFetch async (params: any) { return weatherFetchAdapter.execute({ params, agent_id: manual-test, }); }; console.log([MCP] weather-fetch Skill registered and ready.);4.3 配置 Vite 构建vite.config.tsimport { defineConfig } from vite; import react from vitejs/plugin-react; export default defineConfig({ plugins: [react()], build: { lib: { entry: src/index.ts, name: WeatherSkill, formats: [es, umd], fileName: (format) weather-skill.${format}.js, }, rollupOptions: { // ✅ 关键externalize 依赖避免打包进 Skill external: [mcp/core, zod], output: { globals: { mcp/core: MCP, zod: Zod, }, }, }, }, // ✅ 关键启用 MCP 的 dev server 支持 server: { port: 3001, // 与主应用3000错开 cors: true, }, });为什么external因为 Skill 的运行时依赖mcp/core必须由 Agent 平台统一提供。如果每个 Skill 都打包自己的副本会导致内存爆炸和版本冲突。这就像前端组件库的peerDependencies—— 你声明但不打包。4.4 构建并发布 Skill两种方式方式一作为 ES Module 直接 import推荐用于内部项目npm run build # 输出dist/weather-skill.es.js在主应用中// main-app/src/agent/index.ts import { WeatherSkill } from weather-skill/dist/weather-skill.es.js; // Agent 初始化时加载 Skill const agent new Agent({ skills: [WeatherSkill], // 注册 Skill 列表 });方式二发布为 npm 包用于跨团队复用# package.json 配置 { name: myorg/weather-skill, version: 1.0.0, main: dist/weather-skill.umd.js, types: dist/types/index.d.ts, exports: { .: { import: ./dist/weather-skill.es.js, require: ./dist/weather-skill.umd.js } }, peerDependencies: { mcp/core: ^1.0.0, zod: ^3.0.0 } }发布后其他团队只需npm install myorg/weather-skill然后在他们的 Agent 初始化代码里import { weatherFetchAdapter } from myorg/weather-skill; const agent new Agent({ skills: [weatherFetchAdapter], });实测心得我们团队采用“内部 npm registry semantic versioning”管理 Skill。每次weather-fetch更新只要manifest.json中的version升级Agent 就会自动检测到新版本并提示用户更新。这比手动替换 JS 文件可靠一百倍。4.5 在主应用中集成 Agent 并调用 Skill假设你的主应用是一个 Vue 3 Pinia 项目!-- App.vue -- script setup langts import { onMounted, ref } from vue; import { Agent } from agent/core; // 假设你用的 Agent 框架 import { weatherFetchAdapter } from myorg/weather-skill; const agent refAgent | null(null); const weatherResult refany(null); onMounted(() { // 初始化 Agent注入 Skill agent.value new Agent({ model: gpt-4-turbo, // 或本地 Ollama 模型 skills: [weatherFetchAdapter], // ✅ 关键这里注入 Skill }); // 发送指令触发 Skill 调用 agent.value.run(请查询上海的实时天气并用 Markdown 格式展示).then((result) { weatherResult.value result; }); }); /script template div v-ifweatherResult h3天气报告/h3 div v-htmlweatherResult.markdown/div /div /template注意agent.run()返回的result对象会包含 Skill 执行的完整 trace{ steps: [{ skill_id: weather-fetch, status: success, data: { ... } }] }。你可以用这个 trace 做审计、做 UI 反馈、做错误重试。5. 前端专属避坑指南那些只有踩过才懂的 Skill 开发陷阱再完美的设计也挡不住真实世界的混乱。以下是我在 12 个 Agent 项目中前端工程师踩过的、最痛的 5 个坑附带解决方案。5.1 坑MCP 调用时Skill 的this指向丢失导致this.$router或this.store报错现象你在executor.ts里写了store.dispatch(setLoading, true)但运行时报Cannot read property dispatch of undefined。根因MCP Adapter 的execute函数是纯函数调用不绑定任何this上下文。store是 Vue 的响应式对象必须在组件实例内才能访问。解法永远不要在 Skill 内部直接访问框架实例。正确做法是将store、router等依赖作为executor的参数注入在adapter.ts中从全局状态如window.__STORE__或Agent实例中获取它们或者用provide/inject机制在 Skill 加载时注入依赖。// ✅ 正确依赖注入模式 export async function executeWeatherFetch( input: z.infertypeof WeatherInputSchema, deps: { store: Store; router: Router } // 显式声明依赖 ) { deps.store.dispatch(ui/setLoading, true); // ... 执行逻辑 }5.2 坑Skill 执行成功但 Agent 收不到结果卡在pending状态现象控制台看到weatherFetchAdapter.execute返回了{ status: success, data: {...} }但 Agent 一直等待不继续下一步。根因MCP 要求 Skill 必须返回Promise且 Promise 必须 resolve 一个符合MCPResultSchema 的对象。如果你在execute里写了return { status: success }同步返回Agent 会认为这是“未完成”。解法所有execute函数必须是async且返回PromiseMCPResult。// ❌ 错误同步返回 execute: (request) { return { status: success, data: {} }; // Agent 会卡住 } // ✅ 正确异步返回 execute: async (request) { return Promise.resolve({ status: success, data: {} }); // 或直接 await 后 return }5.3 坑多个 Skill 同时调用DOM 操作冲突页面闪动或报错现象Agent 同时调用weather-fetch和calendar-read两个 Skill 都试图document.getElementById(app).innerHTML ...导致内容覆盖。根因Skill 是并发执行的没有默认的 DOM 操作锁。前端的“单线程”假象在 Agent 的多任务调度下被打破。解法为每个 Skill 分配独立的 DOM 沙箱区域或使用虚拟 DOM 更新。// ✅ 推荐用 ReactDOM.render 替代 innerHTMLReact 项目 import { createRoot } from react-dom/client; import WeatherCard from ./ui/WeatherCard; export const renderWeatherUI (data: any) { const container document.getElementById(weather-sandbox); if (!container) return; const root createRoot(container); root.render(WeatherCard data{data} /); };5.4 坑Skill 的manifest.json修改后Agent 仍加载旧版本现象你更新了manifest.json的version但 Agent 控制台日志显示Loaded skill: weather-fetch1.0.0旧版。根因浏览器缓存了 Skill 的 JS 文件。manifest.json只是元数据JS 文件才是执行体。解法在构建时为 JS 文件添加 contenthash。// vite.config.ts build: { rollupOptions: { output: { assetFileNames: assets/[name].[hash].[ext], chunkFileNames: assets/[name].[hash].js, entryFileNames: assets/[name].[hash].js, } } }同时在registerSkill前加一行 cache-busting// src/index.ts const script document.createElement(script); script.src /weather-skill.js?v${Date.now()}; // 强制刷新 document.head.appendChild(script);5.5 坑LLM 生成的 Skill 调用参数类型与 Zod Schema 严重不符导致大量invalid_input现象Agent 让 Skill 查city: Beijing, China但 Schema 要求city: stringZod 报错Expected string, received object。根因LLM 的 JSON 生成能力不稳定尤其对嵌套对象、数组、特殊字符处理差。解法在 Adapter 层加一层“柔性解析”Lenient Parsing。// ✅ 在 adapter.ts 中 import { parseLenient } from zod/lib/parseLenient; execute: async (request) { try { // 使用 lenient 模式自动转换类型 const validatedInput parseLenient(WeatherInputSchema, request.params); // ... rest } catch (error) { // fallback尝试字符串化再解析 const fallbackParams JSON.parse(JSON.stringify(request.params)); const validatedInput WeatherInputSchema.parse(fallbackParams); } }最后一个小技巧在manifest.json的description字段里用自然语言明确告诉 LLM 该怎么填参数。例如description: 根据城市名纯字符串如 上海不要带国家名获取天气。这比 Schema 更有效。6. 技术选型对比为什么前端应该用 MCP/Skill而不是直接调 API面对 Agent 开发前端工程师常纠结我直接用fetch调后端 API不就行了吗何必搞 MCP/Skill 这一套这是个好问题。下面用一张表说清本质差异维度直接调用 API传统方式MCP SkillAgent 方式前端视角解读可发现性需要硬编码 URL 和参数规则Agent 通过manifest.json自动发现能力列表就像package.json之于 npm不用记每个包的入口文件可组合性每次调用都是独立请求难串联Agent 可规划多 Skill 顺序/并行执行自动处理依赖类似Promise.all()async/await的高级编排错误处理try/catch捕获错误信息杂乱统一status字段failed/cancelled/rate_limited就像 Axios 的response.status不再 parse 字符串权限控制依赖后端 JWT 鉴权前端无感Skill manifest 明确声明permissionsAgent 运行时校验类似button v-ifhasPermission(edit)的声明式权限调试体验查 Network Tab看 raw responseAgent 提供execution trace可视化每一步 Skill 调用类似 Vue Devtools 的组件树但针对“能力调用链”复用成本同一 API不同页面要写 N 次fetch一个 Skill所有 AgentWeb/桌面/移动端共用就像一个 Vue 组件到处import就能用最关键的一点MCP/Skill 让前端工程师从“API 消费者”升级为“能力提供者”。你不再只是写useWeatherApi()这样的 Hook而是定义weather-fetch这个能力实体它有自己的 ID、版本、文档、测试用例、错误码体系。当产品说“我们要支持语音查天气”你不需要改一行业务代码只需新增一个weather-speechSkillAgent 自动学会用语音触发它。这才是前端在 AI 时代的核心护城河不是写更多 JS而是定义更多可被智能体理解、调度、组合的“数字劳动力”。7. 下一步用 Skill 构建你的第一个 Agent 工作流现在你已经掌握了 MCP 的本质、Skill 的编写范式、以及避坑实战经验。下一步不是学更多理论而是立刻动手构建一个真实的、解决工作痛点的 Agent 工作流。我建议你从这个最小可行工作流开始“会议纪要生成 Agent”输入一段 Zoom 录音文字稿或粘贴的聊天记录输出结构化 Markdown 纪要含结论、待办、负责人、截止时间技能链text-summarizeSkill调用 LLM API→ 生成摘要extract-action-itemsSkill正则 LLM→ 提取待办assign-ownerSkill规则引擎→ 根据姓名匹配负责人insert-to-notionSkillNotion API→ 自动创建待办卡片这个工作流完全可以用你刚学会的 Skill 模式实现每个 Skill 独立开发、独立测试、独立发布manifest.json里声明permissions: [notion:write]Agent 的规划器Planner自动识别步骤依赖串起四步调用你作为前端只负责写insert-to-notion的 DOM 渲染反馈和assign-owner的规则逻辑。别等“完美方案”。今天就用vite create新建一个meeting-minutes-skill项目写完manifest.json和schema.ts你就已经走在了 90% 前端同行前面。最后分享一个真实体会在我转型做 Agent 开发的第 37 天我用 Skill 封装了公司内部的“请假审批”流程。以前 HR 要手动查系统、填表格、发邮件现在员工对 Agent 说一句“我要请三天年假”Agent 自动走完全部流程HR 只需在最后一步点“批准”。那个瞬间我意识到前端工程师的终极价值从来不是写多少行代码而是让复杂流程变得像点击按钮一样简单。而 Skill就是我们递给 AI 的那个按钮。
返回列表