OpenClaw MCP 技能设计文档

2026年03月11日5 次阅读0 人喜欢
OpenClawMCP技能设计OAuth 2.0AI

OpenClaw MCP 技能设计文档

创建时间: 2026-03-11
目标: 为博客 MCP 服务构建 OpenClaw 技能,使 OpenClaw 用户能够通过 AI 助手管理博客内容


? 背景

OpenClaw 对 MCP 的支持情况

核心发现: OpenClaw 不原生支持远程 MCP 服务(HTTP/SSE/WebSocket),主要通过以下方式支持 MCP:

  1. mcporter - 官方推荐的 MCP 桥接工具

    • 支持本地 MCP 服务器(通过 stdio transport)
    • 允许动态添加/修改 MCP 服务器
    • 不支持远程 HTTP MCP 服务器
  2. MCP 技能 - 社区解决方案

    • mcp-integration skill
    • 需要自定义实现远程 MCP 服务器连接

我们的博客 MCP 服务特点

  • Transport: HTTP POST (Stateless Request-Response)
  • 认证: OAuth 2.0 Bearer Token (LTK_ 前缀的长期 Token)
  • 端点: /api/mcp
  • 工具: 6 个工具(创建、更新、删除、获取、列出文章)
  • 资源: 5+ 个资源(标签、合集、写作风格、参考文档)

? 设计目标

核心目标

为 OpenClaw 创建一个技能,使得:

  1. 透明集成: 用户可以通过自然语言指令管理博客
  2. 完整功能: 支持所有 MCP 工具和资源
  3. 无缝认证: 自动处理 OAuth 2.0 认证
  4. 错误处理: 提供清晰的错误提示和重试机制
  5. 可扩展: 易于添加新的 MCP 功能

技术约束

  1. OpenClaw 技能使用 TypeScript/JavaScript
  2. 需要 HTTP 客户端 来调用远程 MCP 服务
  3. 需要处理 JSON-RPC 2.0 协议
  4. 需要管理 认证 Token 的生命周期

?️ 架构设计

技能架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                        OpenClaw                             │
│                    (AI Agent Runtime)                       │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│                   Blog Manager Skill                        │
│                                                             │
│  ┌──────────────────────────────────────────────────────┐  │
│  │            Natural Language Processor                │  │
│  │   (解析用户意图,提取参数,验证输入)                  │  │
│  └────────────────────┬─────────────────────────────────┘  │
│                       │                                      │
│  ┌────────────────────▼─────────────────────────────────┐  │
│  │              MCP Client Adapter                      │  │
│  │   (JSON-RPC 2.0 协议封装,HTTP 请求管理)            │  │
│  └────────────────────┬─────────────────────────────────┘  │
│                       │                                      │
│  ┌────────────────────▼─────────────────────────────────┐  │
│  │           Authentication Manager                     │  │
│  │   (OAuth 2.0 Bearer Token 管理,自动刷新)           │  │
│  └────────────────────┬─────────────────────────────────┘  │
│                       │                                      │
└───────────────────────┼──────────────────────────────────────┘
                        │
                        ▼
┌─────────────────────────────────────────────────────────────┐
│              Blog MCP Server (Remote)                       │
│                 /api/mcp (HTTP POST)                        │
└─────────────────────────────────────────────────────────────┘

模块划分

1. Natural Language Processor (NLP)

  • 解析用户自然语言指令
  • 提取操作类型(创建/更新/删除/查询)
  • 验证和清理输入参数
  • 提供智能默认值

2. MCP Client Adapter

  • 实现 JSON-RPC 2.0 协议
  • 处理 HTTP 请求/响应
  • 支持批量操作
  • 错误处理和重试逻辑

3. Authentication Manager

  • 管理 OAuth 2.0 Bearer Token
  • Token 存储和检索
  • 自动 Token 刷新(如需要)
  • 认证失败处理

4. Resource Cache

  • 缓存标签列表
  • 缓存合集列表
  • 缓存写作风格指南
  • 智能缓存失效

? 技能功能设计

支持的操作

1. 文章管理

创建文章
复制代码
用户指令示例:
- "帮我创建一篇关于 TypeScript 的文章"
- "写一篇博客,主题是 React 19 新特性"
- "发布一篇技术文章,介绍 Next.js 16"

参数提取:
- title: 从主题推断或请求用户输入
- content: 引导用户提供或草稿
- tags: 建议基于内容的标签
- category: 技术文章/随笔
- collections: 询问是否添加到合集
更新文章
复制代码
用户指令示例:
- "更新第 123 篇文章的标题"
- "修改文章内容,添加新章节"
- "把这篇文章添加到 '小破站建设' 合集"

参数提取:
- id: 从上下文推断或请求
- 要更新的字段:动态提取
- add_to_collections: 解析合集名称
删除文章
复制代码
用户指令示例:
- "删除文章 123"
- "移除这篇草稿"

安全确认:
- 二次确认
- 显示文章标题
查询文章
复制代码
用户指令示例:
- "列出最近的文章"
- "搜索关于 MCP 的文章"
- "显示第 5 页的文章"

参数:
- pageNum, pageSize
- keyword
- hide (显示/隐藏)

2. 资源查询

复制代码
用户指令示例:
- "显示所有标签"
- "有哪些合集?"
- "写作风格指南是什么?"
- "查看路由参考文档"

自动缓存资源,减少重复请求

交互流程

创建文章完整流程

复制代码
1. 用户: "帮我创建一篇关于 MCP 的技术文章"

2. 技能响应流程:
   a) 解析意图 → 创建文章
   b) 检查资源 → 获取标签和合集列表
   c) 读取写作风格指南
   d) 生成草稿或引导用户输入
   e) 展示预览
   f) 确认后创建

3. 成功后:
   - 显示文章链接
   - 询问是否添加到合集
   - 建议相关标签

智能建议

复制代码
基于上下文提供智能建议:

1. 标签建议
   - 从内容提取关键词
   - 与现有标签匹配
   - 推荐热门标签

2. 合集建议
   - 基于分类推荐
   - 显示合集描述
   - 显示文章数量

3. 写作建议
   - 风格一致性检查
   - 结构建议
   - 长度建议

? 认证设计

Token 管理

配置存储

typescript 复制代码
// OpenClaw 技能配置
interface BlogSkillConfig {
  mcpEndpoint: string;      // "https://react.nnnnzs.cn/api/mcp"
  authToken: string;        // LTK_xxx
  userId?: number;          // 可选,用于日志
}

// 存储位置
// ~/.openclaw/skills/blog-manager/config.json

Token 获取流程

复制代码
1. 首次使用时引导用户:
   "请提供您的博客 MCP Token(从 /c/user/info 生成)"

2. 验证 Token:
   - 调用 tools/list 测试
   - 成功则存储
   - 失败则提示重新输入

3. Token 存储:
   - 本地加密存储
   - 环境变量选项
   - 不在日志中显示

认证失败处理

复制代码
1. 捕获 401 错误
2. 提示用户:
   "Token 已过期或无效,请重新生成"
3. 提供重新配置选项
4. 清除无效 Token

? 实现细节

技能目录结构

复制代码
~/.openclaw/skills/blog-manager/
├── package.json
├── tsconfig.json
├── README.md
├── src/
│   ├── index.ts                 # 技能入口
│   ├── config.ts                # 配置管理
│   ├── auth.ts                  # 认证管理
│   ├── mcp-client.ts            # MCP 客户端
│   ├── nlp/                     # 自然语言处理
│   │   ├── index.ts
│   │   ├── intent-parser.ts     # 意图解析
│   │   └── param-extractor.ts   # 参数提取
│   ├── handlers/                # 操作处理器
│   │   ├── article.ts           # 文章操作
│   │   ├── resource.ts          # 资源查询
│   │   └── cache.ts             # 缓存管理
│   ├── prompts/                 # 提示词模板
│   │   ├── create-article.md
│   │   ├── update-article.md
│   │   └── confirm-delete.md
│   └── types.ts                 # 类型定义
└── test/
    ├── mcp-client.test.ts
    └── nlp.test.ts

核心:MCP 客户端实现

typescript 复制代码
// src/mcp-client.ts
import axios from 'axios';

export class BlogMCPClient {
  private endpoint: string;
  private token: string;

  constructor(endpoint: string, token: string) {
    this.endpoint = endpoint;
    this.token = token;
  }

  /**
   * 调用 MCP 工具
   */
  async callTool(toolName: string, args: Record<string, any>) {
    const response = await axios.post(
      this.endpoint,
      {
        jsonrpc: '2.0',
        id: Date.now(),
        method: 'tools/call',
        params: {
          name: toolName,
          arguments: args
        }
      },
      {
        headers: {
          'Authorization': `Bearer ${this.token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    if (response.data.error) {
      throw new MCPError(response.data.error);
    }

    return response.data.result;
  }

  /**
   * 读取 MCP 资源
   */
  async readResource(uri: string) {
    const response = await axios.post(
      this.endpoint,
      {
        jsonrpc: '2.0',
        id: Date.now(),
        method: 'resources/read',
        params: { uri }
      },
      {
        headers: {
          'Authorization': `Bearer ${this.token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.data.result;
  }

  /**
   * 列出可用工具
   */
  async listTools() {
    const response = await axios.post(
      this.endpoint,
      {
        jsonrpc: '2.0',
        id: Date.now(),
        method: 'tools/list'
      },
      {
        headers: {
          'Authorization': `Bearer ${this.token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.data.result;
  }

  /**
   * 列出可用资源
   */
  async listResources() {
    const response = await axios.post(
      this.endpoint,
      {
        jsonrpc: '2.0',
        id: Date.now(),
        method: 'resources/list'
      },
      {
        headers: {
          'Authorization': `Bearer ${this.token}`,
          'Content-Type': 'application/json'
        }
      }
    );

    return response.data.result;
  }
}

class MCPError extends Error {
  constructor(public error: any) {
    super(error.message || 'MCP Error');
    this.name = 'MCPError';
  }
}

核心:技能入口

typescript 复制代码
// src/index.ts
import { PluginApi } from '@openclaw/sdk';
import { BlogMCPClient } from './mcp-client';
import { ConfigManager } from './config';
import { ArticleHandler } from './handlers/article';
import { ResourceHandler } from './handlers/resource';
import { IntentParser } from './nlp/intent-parser';

export default async function register(api: PluginApi) {
  const config = await ConfigManager.load();
  const client = new BlogMCPClient(config.endpoint, config.token);

  // 注册技能
  api.registerSkill({
    id: 'blog-manager',
    name: 'Blog Manager',
    description: '管理博客文章、标签和合集',
    version: '1.0.0',

    // 配置处理
    async configure(params) {
      if (params.endpoint) config.endpoint = params.endpoint;
      if (params.token) config.token = params.token;
      await config.save();
      return { success: true };
    },

    // 主处理函数
    async handle(input, context) {
      const parser = new IntentParser();
      const intent = await parser.parse(input);

      switch (intent.action) {
        case 'create_article':
          return await ArticleHandler.create(client, intent, context);

        case 'update_article':
          return await ArticleHandler.update(client, intent, context);

        case 'delete_article':
          return await ArticleHandler.delete(client, intent, context);

        case 'list_articles':
          return await ArticleHandler.list(client, intent, context);

        case 'get_article':
          return await ArticleHandler.get(client, intent, context);

        case 'list_resources':
          return await ResourceHandler.list(client, intent);

        case 'read_resource':
          return await ResourceHandler.read(client, intent);

        default:
          return {
            text: `我不太理解您想做什么。可以尝试:
            - 创建/更新/删除文章
            - 查询文章列表
            - 查看标签或合集`
          };
      }
    }
  });

  // 注册命令
  api.registerCommand({
    name: 'blog',
    description: '博客管理命令',
    handler: async (args, context) => {
      return await api.skills['blog-manager'].handle(args.join(' '), context);
    }
  });
}

意图解析

typescript 复制代码
// src/nlp/intent-parser.ts
export interface Intent {
  action: string;
  params: Record<string, any>;
  confidence: number;
}

export class IntentParser {
  async parse(input: string): Promise<Intent> {
    const lower = input.toLowerCase();

    // 创建文章
    if (this.matchesCreate(lower)) {
      return {
        action: 'create_article',
        params: this.extractCreateParams(input),
        confidence: 0.9
      };
    }

    // 更新文章
    if (this.matchesUpdate(lower)) {
      return {
        action: 'update_article',
        params: this.extractUpdateParams(input),
        confidence: 0.9
      };
    }

    // 删除文章
    if (this.matchesDelete(lower)) {
      return {
        action: 'delete_article',
        params: this.extractDeleteParams(input),
        confidence: 0.95
      };
    }

    // 列出文章
    if (this.matchesList(lower)) {
      return {
        action: 'list_articles',
        params: this.extractListParams(input),
        confidence: 0.9
      };
    }

    // 获取文章
    if (this.matchesGet(lower)) {
      return {
        action: 'get_article',
        params: this.extractGetParams(input),
        confidence: 0.9
      };
    }

    // 资源查询
    if (this.matchesResourceQuery(lower)) {
      return {
        action: this.extractResourceAction(lower),
        params: {},
        confidence: 0.85
      };
    }

    // 默认:请求澄清
    return {
      action: 'clarify',
      params: { originalInput: input },
      confidence: 0.5
    };
  }

  private matchesCreate(input: string): boolean {
    return /^(创建|写|发布|新增|添加).*文章/.test(input) ||
           /blog.*post/i.test(input) ||
           /new.*article/i.test(input);
  }

  private matchesUpdate(input: string): boolean {
    return /^(更新|修改|编辑).*文章/.test(input) ||
           /update.*article/i.test(input);
  }

  private matchesDelete(input: string): boolean {
    return /^(删除|移除).*文章/.test(input) ||
           /delete.*article/i.test(input);
  }

  private matchesList(input: string): boolean {
    return /^列出|显示|查看.*文章/.test(input) ||
           /list.*articles?/i.test(input);
  }

  private matchesGet(input: string): boolean {
    return /^获取|查询|搜索.*文章/.test(input) ||
           /get.*article/i.test(input);
  }

  private matchesResourceQuery(input: string): boolean {
    return /标签|合集|写作风格|参考文档/.test(input) ||
           /tags?|collections?|writing.*style|reference/i.test(input);
  }

  private extractCreateParams(input: string): Record<string, any> {
    const params: any = {};

    // 提取主题/标题
    const titleMatch = input.match(/(?:关于|主题是|标题是|名为)\s*[""]?(.+?)[""]?(?:\s|$|的)/);
    if (titleMatch) {
      params.title = titleMatch[1].trim();
    }

    // 提取分类
    if (/技术|教程|指南/.test(input)) {
      params.category = '技术文章';
    } else if (/随笔|日记|感想/.test(input)) {
      params.category = '随笔';
    }

    return params;
  }

  private extractUpdateParams(input: string): Record<string, any> {
    const params: any = {};

    // 提取文章 ID
    const idMatch = input.match(/第\s*(\d+)\s*篇|文章\s*(\d+)|#(\d+)/);
    if (idMatch) {
      params.id = parseInt(idMatch[1] || idMatch[2] || idMatch[3]);
    }

    return params;
  }

  private extractDeleteParams(input: string): Record<string, any> {
    const params: any = {};

    // 提取文章 ID
    const idMatch = input.match(/(\d+)/);
    if (idMatch) {
      params.id = parseInt(idMatch[1]);
    }

    return params;
  }

  private extractListParams(input: string): Record<string, any> {
    const params: any = { pageNum: 1, pageSize: 10 };

    // 提取页码
    const pageMatch = input.match(/第\s*(\d+)\s*页/);
    if (pageMatch) {
      params.pageNum = parseInt(pageMatch[1]);
    }

    // 提取关键词
    const keywordMatch = input.match(/(?:搜索|查找|关于)\s*(.+?)(?:\s|$|的)/);
    if (keywordMatch) {
      params.keyword = keywordMatch[1].trim();
    }

    return params;
  }

  private extractGetParams(input: string): Record<string, any> {
    const params: any = {};

    // 提取 ID 或标题
    const idMatch = input.match(/(\d+)/);
    if (idMatch) {
      params.id = parseInt(idMatch[1]);
    } else {
      const titleMatch = input.match(/(?:标题|名为)\s*[""]?(.+?)[""]?(?:\s|$)/);
      if (titleMatch) {
        params.title = titleMatch[1].trim();
      }
    }

    return params;
  }

  private extractResourceAction(input: string): string {
    if (input.includes('标签') || input.includes('tag')) {
      return 'list_tags';
    }
    if (input.includes('合集') || input.includes('collection')) {
      return 'list_collections';
    }
    if (input.includes('写作风格') || input.includes('writing')) {
      return 'read_writing_style';
    }
    return 'list_resources';
  }
}

文章处理器

typescript 复制代码
// src/handlers/article.ts
import { BlogMCPClient } from '../mcp-client';
import { Intent } from '../nlp/intent-parser';

export class ArticleHandler {
  /**
   * 创建文章
   */
  static async create(client: BlogMCPClient, intent: Intent, context: any) {
    // 1. 获取可用资源
    const [tags, collections, styleGuide] = await Promise.all([
      client.readResource('blog://tags'),
      client.readResource('blog://collections'),
      client.readResource('blog://writing_style')
    ]);

    // 2. 如果没有提供完整参数,引导用户
    if (!intent.params.title) {
      return {
        text: `请提供文章标题。当前可用的标签:${tags.contents[0].text}`,
        suggestions: [
          { action: 'input', label: '输入标题' },
          { action: 'cancel', label: '取消' }
        ]
      };
    }

    // 3. 检查写作风格
    const style = styleGuide.contents[0].text;
    const styleTips = this.extractStyleTips(style);

    // 4. 引导输入内容或生成草稿
    if (!intent.params.content) {
      return {
        text: `好的,我将创建文章《${intent.params.title}》。

${styleTips}

请提供文章内容,或者告诉我你想写什么,我可以帮你生成草稿。

当前可用的标签:${tags.contents[0].text}
当前可用的合集:${collections.contents[0].text}
`
      };
    }

    // 5. 调用 MCP 工具创建
    const result = await client.callTool('create_article', {
      title: intent.params.title,
      content: intent.params.content,
      category: intent.params.category || '技术文章',
      tags: intent.params.tags,
      collections: intent.params.collections,
      description: intent.params.description,
      cover: intent.params.cover
    });

    // 6. 格式化响应
    const article = JSON.parse(result.content[0].text);
    return {
      text: `✅ 文章创建成功!

标题:${article.title}
链接:${article.path}
ID:${article.id}

${article.path ? `? 查看文章: ${article.path}` : ''}

还需要做什么?
- 添加到合集?告诉我合集名称
- 添加标签?告诉我标签名称
- 修改内容?告诉我怎么改
`
    };
  }

  /**
   * 更新文章
   */
  static async update(client: BlogMCPClient, intent: Intent, context: any) {
    if (!intent.params.id) {
      return {
        text: '请提供要更新的文章 ID(例如:更新文章 123)'
      };
    }

    // 获取文章当前状态
    const current = await client.callTool('get_article', {
      id: intent.params.id
    });

    if (current.isError) {
      return {
        text: `找不到文章 ID ${intent.params.id}`
      };
    }

    const article = JSON.parse(current.content[0].text);

    // 调用 MCP 工具更新
    const result = await client.callTool('update_article', {
      id: intent.params.id,
      ...intent.params
    });

    const updated = JSON.parse(result.content[0].text);
    return {
      text: `✅ 文章更新成功!

标题:${updated.title}
链接:${updated.path}
`
    };
  }

  /**
   * 删除文章
   */
  static async delete(client: BlogMCPClient, intent: Intent, context: any) {
    if (!intent.params.id) {
      return {
        text: '请提供要删除的文章 ID(例如:删除文章 123)'
      };
    }

    // 二次确认
    if (!context.confirmed) {
      const current = await client.callTool('get_article', {
        id: intent.params.id
      });

      if (current.isError) {
        return {
          text: `找不到文章 ID ${intent.params.id}`
        };
      }

      const article = JSON.parse(current.content[0].text);
      return {
        text: `⚠️ 即将删除文章:

标题:${article.title}
ID:${article.id}

此操作不可撤销!确认删除吗?`,
        confirm: true
      };
    }

    // 执行删除
    const result = await client.callTool('delete_article', {
      id: intent.params.id
    });

    return {
      text: `✅ 文章已删除(ID: ${intent.params.id})`
    };
  }

  /**
   * 列出文章
   */
  static async list(client: BlogMCPClient, intent: Intent, context: any) {
    const result = await client.callTool('list_articles', intent.params);
    const data = JSON.parse(result.content[0].text);

    let output = `? 文章列表 (第 ${data.pageNum} 页,共 ${data.total} 篇)\n\n`;

    data.record.forEach((post: any, index: number) => {
      output += `${index + 1}. ${post.title}\n`;
      output += `   ID: ${post.id} | ${post.path || ''}\n`;
      if (post.summary) output += `   ${post.summary}\n`;
      output += '\n';
    });

    return {
      text: output,
      pagination: {
        current: data.pageNum,
        total: data.totalPages,
        hasMore: data.pageNum < data.totalPages
      }
    };
  }

  /**
   * 获取文章
   */
  static async get(client: BlogMCPClient, intent: Intent, context: any) {
    const result = await client.callTool('get_article', intent.params);

    if (result.isError) {
      return {
        text: `获取文章失败:${result.content[0].text}`
      };
    }

    const article = JSON.parse(result.content[0].text);
    return {
      text: `? ${article.title}

ID: ${article.id}
分类: ${article.category || '未分类'}
标签: ${article.tags || '无'}
创建时间: ${article.created_at}

${article.summary ? `摘要:${article.summary}\n\n` : ''}${article.content}

${article.path ? `\n? 链接: ${article.path}` : ''}`
    };
  }

  private static extractStyleTips(styleGuide: string): string {
    // 从写作风格指南中提取关键提示
    return `? 写作提示:
- 使用第一人称("我")
- 保持对话式、非正式的语气
- 短句为主,避免过长段落
- 真诚、自然`;
  }
}

资源处理器

typescript 复制代码
// src/handlers/resource.ts
import { BlogMCPClient} from '../mcp-client';
import { Intent } from '../nlp/intent-parser';

export class ResourceHandler {
  /**
   * 列出资源
   */
  static async list(client: BlogMCPClient, intent: Intent) {
    const result = await client.listResources();

    let output = '? 可用资源:\n\n';
    result.resources.forEach((resource: any) => {
      output += `- ${resource.name}\n`;
      output += `  URI: ${resource.uri}\n`;
      if (resource.description) {
        output += `  ${resource.description}\n`;
      }
      output += '\n';
    });

    return {
      text: output
    };
  }

  /**
   * 读取特定资源
   */
  static async read(client: BlogMCPClient, intent: Intent) {
    let uri: string;

    switch (intent.action) {
      case 'list_tags':
        uri = 'blog://tags';
        break;
      case 'list_collections':
        uri = 'blog://collections';
        break;
      case 'read_writing_style':
        uri = 'blog://writing_style';
        break;
      default:
        uri = intent.params.uri;
    }

    const result = await client.readResource(uri);

    if (result.isError) {
      return {
        text: `读取资源失败:${result.content[0].text}`
      };
    }

    const content = result.contents[0];

    // 根据资源类型格式化输出
    if (uri === 'blog://tags') {
      const tags = content.text.split(',');
      return {
        text: `?️ 可用标签 (${tags.length} 个):\n\n${tags.join(', ')}`
      };
    }

    if (uri === 'blog://collections') {
      return {
        text: `? 可用合集:\n\n${content.text}`
      };
    }

    if (uri === 'blog://writing_style') {
      return {
        text: `? 写作风格指南:\n\n${content.text.substring(0, 500)}...\n\n(完整指南请参考博客文档)`
      };
    }

    return {
      text: content.text
    };
  }
}

? 使用文档

安装技能

bash 复制代码
# 1. 克隆技能仓库
git clone https://github.com/NNNNzs/openclaw-blog-manager.git ~/.openclaw/skills/blog-manager

# 2. 安装依赖
cd ~/.openclaw/skills/blog-manager
pnpm install

# 3. 构建
pnpm build

# 4. 配置
openclaw skill configure blog-manager

配置技能

bash 复制代码
# 方式 1: 通过命令行配置
openclaw skill config blog-manager --endpoint https://react.nnnnzs.cn/api/mcp
openclaw skill config blog-manager --token LTK_xxx

# 方式 2: 通过配置文件
# 编辑 ~/.openclaw/skills/blog-manager/config.json
{
  "endpoint": "https://react.nnnnzs.cn/api/mcp",
  "token": "LTK_xxx"
}

使用示例

在 OpenClaw Gateway 中

复制代码
你: 帮我创建一篇关于 OpenClaw 的技术文章

OpenClaw: 好的,我将创建文章《OpenClaw》。

? 写作提示:
- 使用第一人称("我")
- 保持对话式、非正式的语气
- 短句为主,避免过长段落
- 真诚、自然

当前可用的标签:TypeScript,React,Next.js,MCP,OAuth
当前可用的合集:
ID: 1 | Slug: xiao-po-zhan-jian-she | Title: 小破站建设 | Articles: 10

请提供文章内容,或者告诉我你想写什么,我可以帮你生成草稿。

你: [提供文章内容或主题]

OpenClaw: ✅ 文章创建成功!

标题:OpenClaw 入门指南
链接:/2026/03/11/openclaw-getting-started
ID: 123

? 查看文章: /2026/03/11/openclaw-getting-started

还需要做什么?
- 添加到合集?告诉我合集名称
- 添加标签?告诉我标签名称

在频道中(WhatsApp/Telegram/Discord)

复制代码
你: /blog 列出最近的文章

Bot: ? 文章列表 (第 1 页,共 25 篇)

1. MCP OAuth 2.0 认证升级总结
   ID: 120 | /2026/01/15/mcp-oauth-upgrade
   这篇文章记录了 MCP 认证从自定义 Headers 到 OAuth 2.0 的完整升级过程...

2. 向量检索系统设计
   ID: 119 | /2026/01/10/vector-search-design
   基于 Qdrant 的语义搜索系统设计文档...

[更多文章...]

---

输入页码查看更多,或使用 /blog 搜索 <关键词> 搜索文章

? 测试策略

单元测试

typescript 复制代码
// test/mcp-client.test.ts
import { BlogMCPClient } from '../src/mcp-client';

describe('BlogMCPClient', () => {
  let client: BlogMCPClient;

  beforeEach(() => {
    client = new BlogMCPClient(
      'https://react.nnnnzs.cn/api/mcp',
      'test_token'
    );
  });

  test('应该成功调用工具', async () => {
    const result = await client.callTool('list_articles', {
      pageNum: 1,
      pageSize: 10
    });

    expect(result).toHaveProperty('content');
  });

  test('应该处理认证错误', async () => {
    const badClient = new BlogMCPClient(
      'https://react.nnnnzs.cn/api/mcp',
      'invalid_token'
    );

    await expect(
      badClient.callTool('list_articles', {})
    ).rejects.toThrow('Authentication failed');
  });
});

集成测试

typescript 复制代码
// test/integration.test.ts
import { IntentParser } from '../src/nlp/intent-parser';

describe('意图解析', () => {
  const parser = new IntentParser();

  test('应该正确解析创建文章意图', async () => {
    const intent = await parser.parse('创建一篇关于 React 的文章');

    expect(intent.action).toBe('create_article');
    expect(intent.params.title).toBe('React');
  });

  test('应该正确解析列出文章意图', async () => {
    const intent = await parser.parse('列出第 2 页的文章');

    expect(intent.action).toBe('list_articles');
    expect(intent.params.pageNum).toBe(2);
  });
});

? 部署与发布

发布到 LobeHub Skills Marketplace

bash 复制代码
# 1. 准备发布
npm run build
npm run pack

# 2. 发布到 GitHub
git tag v1.0.0
git push --tags

# 3. 提交到 LobeHub
# 访问 https://lobehub.com/skills/publish
# 填写技能信息和仓库链接

技能元数据

json 复制代码
{
  "name": "blog-manager",
  "displayName": "Blog Manager",
  "description": "管理博客文章、标签和合集",
  "version": "1.0.0",
  "author": "NNNNzs",
  "license": "MIT",
  "homepage": "https://github.com/NNNNzs/openclaw-blog-manager",
  "repository": "https://github.com/NNNNzs/openclaw-blog-manager",
  "openclaw": {
    "minVersion": "2026.3.0",
    "permissions": [
      "network",
      "storage"
    ]
  },
  "keywords": [
    "blog",
    "cms",
    "mcp",
    "article"
  ]
}

? 参考资料


? 后续优化

短期优化

  1. 批量操作: 支持批量创建/更新文章
  2. 图片上传: 支持上传封面图
  3. 草稿保存: 保存未完成的文章草稿
  4. 快捷命令: 定义常用操作的快捷方式

长期优化

  1. 本地缓存: 实现本地文章缓存,离线查看
  2. 多用户支持: 支持切换不同的博客账户
  3. 统计分析: 文章阅读量、标签热度统计
  4. 自动发布: 定时发布、社交媒体同步

状态: ? 设计中
下一步: 实现核心 MCP 客户端和意图解析器
预计完成: 2026-03-20

加载评论中...