diff --git a/SKILL.md b/SKILL.md index 4b0df5c..dc24493 100644 --- a/SKILL.md +++ b/SKILL.md @@ -20,23 +20,36 @@ description: 提供 Educoder 用户与课堂相关查询能力,当前支持用 ## 前置配置 -接入 OpenClaw 时,多个会话使用同一个 `Authorization` token。调用端每次建立 MCP 连接时,都从环境变量 `EDUCODER_AUTHORIZATION` 读取 token,并放到请求头里。 +接入 OpenClaw 时,多个会话使用同一个 `Authorization` token。第一次安装 skill 时,把 MCP 服务地址和 token 一起写到 OpenClaw 的 MCP 配置文件中;后续调用 MCP 时,从配置文件读取 token 并放到请求头里。 ### Authorization 配置 -在运行 OpenClaw 或 MCP 客户端的进程环境中配置。下面的 `export` 只对当前 shell 或当前进程有效,适合临时调试: +安装 skill 时要求把用户输入 Educoder `Authorization` token写入 MCP 配置文件。 -```bash -export EDUCODER_AUTHORIZATION="xxxxx" +MCP 配置示例: + +```json +{ + "educoder-mcp": { + "url": "http://47.98.32.66:48000/mcp", + "transport": "streamable-http", + "headers": { + "Authorization": "xxxxx" + } + } +} ``` -OpenClaw 线上使用时,必须把 token 保存到 OpenClaw 的持久化配置或密钥存储中,并在启动服务、创建新会话或刷新 token 后注入到进程环境变量 `EDUCODER_AUTHORIZATION`。不要只依赖临时 `export`,否则新开会话或服务重启后会读不到 token。 +> `Authorization` 的值直接写 token 原文,不要写成 `Bearer xxxxx`。不要把 token 放到工具参数里。 -MCP 客户端每次连接时应读取环境变量并注入: +后续调用 MCP 时,从 MCP 配置读取 `headers.Authorization` 并注入请求头: ```python +mcp_config = openclaw_mcp_config.get("educoder-mcp") +token = mcp_config["headers"]["Authorization"] + headers = { - "Authorization": os.getenv("EDUCODER_AUTHORIZATION", "").strip() + "Authorization": token } ``` @@ -52,7 +65,7 @@ Authorization: xxxxx Authorization: Bearer xxxxx ``` -然后在创建 MCP 连接时透传: +创建 MCP 连接时透传: ```python async with streamable_http_client(self.server_url, headers=headers or None) as (read, write, _): @@ -63,28 +76,19 @@ async with streamable_http_client(self.server_url, headers=headers or None) as ( ### 多会话共享 Token -多个会话共用同一个 `EDUCODER_AUTHORIZATION`。不要让用户在每个会话里单独输入 token,也不要把 token 放到工具参数里。 - -正确做法是: - -1. 在 OpenClaw 持久化配置或密钥存储中保存 token -2. OpenClaw 启动服务或创建新会话时,把保存的 token 注入环境变量 `EDUCODER_AUTHORIZATION` -3. 每次创建 MCPClient 或建立 MCP 连接时,从 `os.getenv("EDUCODER_AUTHORIZATION")` 读取最新值 -4. 把读取到的 token 原文放进 MCP transport headers 的 `Authorization`,不要添加 `Bearer ` 前缀 -5. 多个会话可以分别创建 MCPClient 实例,但认证 token 都来自同一个环境变量 +多个会话共用 MCP 配置文件中的同一个 `Authorization` token。安装后不需要每个会话重复输入 token;每次调用 MCP 时读取配置里的 `headers.Authorization` 即可。 ### 401 Token 过期处理 -如果调用 MCP 工具或后端接口时返回 `401 Unauthorized`,说明当前 `EDUCODER_AUTHORIZATION` 已过期或无效。 +如果调用 MCP 工具或后端接口时返回 `401 Unauthorized`,说明当前 token 已过期或无效。 处理流程: 1. 停止继续使用当前 token 重试 2. 提示用户重新输入新的 `Authorization` -3. 将用户输入的新 token 保存到 OpenClaw 的持久化配置或密钥存储 -4. 同步更新当前进程环境变量 `EDUCODER_AUTHORIZATION` -5. 重新创建 MCP 连接,并从环境变量读取最新 token -6. 使用新 token 重新发起本次工具调用 +3. 将用户输入的新 token 写入 OpenClaw 的 MCP 配置文件 +4. 重新创建 MCP 连接,并从 MCP 配置读取最新 token +5. 使用新 token 重新发起本次工具调用 示例提示: @@ -92,36 +96,28 @@ async with streamable_http_client(self.server_url, headers=headers or None) as ( 当前 Educoder 授权已过期,请重新输入 Authorization token。 ``` -更新环境变量示例: - -```bash -export EDUCODER_AUTHORIZATION="new-token" -``` - -> `export` 只是临时示例。线上需要同时更新持久化配置,否则新开会话或服务重启后仍会丢失 token。更新 token 后必须重新建立 MCP 连接,不能复用已经携带旧 headers 的连接。 +> 更新 token 后必须重新建立 MCP 连接,不能复用已经携带旧 headers 的连接。 ### OpenClaw 接入建议 如果使用 OpenClaw,推荐按下面的方式接入: 1. 安装 skill 时,MCP 服务地址填写:`http://47.98.32.66:48000/mcp` -2. 在 OpenClaw 持久化配置或密钥存储中保存 token -3. OpenClaw 启动服务或创建新会话时,把保存的 token 注入环境变量 `EDUCODER_AUTHORIZATION` +2. 安装 skill 时,要求用户输入 Educoder `Authorization` token +3. OpenClaw 将 MCP 地址和 token 写入 MCP 配置文件,其中 token 放到 `headers.Authorization` 4. 当前用户发起工具调用时,后端创建 MCPClient -5. MCPClient 建连时从环境变量读取 `EDUCODER_AUTHORIZATION` +5. MCPClient 建连时从 MCP 配置读取 token 6. MCPClient 把读取到的 token 原文放进 MCP headers,不要添加 `Bearer ` 前缀 -7. 如果调用返回 `401 Unauthorized`,提示用户重新输入 token,更新持久化配置和 `EDUCODER_AUTHORIZATION` 后重新建连并重试 +7. 如果调用返回 `401 Unauthorized`,提示用户重新输入 token,更新 MCP 配置后重新建连并重试 参考流程: ```python -import os - tool_server_url = "http://47.98.32.66:48000/mcp" -client = MCPClient( - server_url=tool_server_url, -) +mcp_config = openclaw_mcp_config.get("educoder-mcp") + +client = MCPClient(mcp_config=mcp_config) tool_result = await client.call_tool(tool_name, tool_arguments) ``` @@ -130,12 +126,13 @@ MCPClient 示例: ```python class MCPClient: - def __init__(self, server_url: str): - self.server_url = server_url + def __init__(self, mcp_config: dict): + self.server_url = mcp_config["url"] + self.mcp_config = mcp_config def _build_headers(self) -> dict[str, str]: headers = {} - authorization = os.getenv("EDUCODER_AUTHORIZATION", "").strip() + authorization = self.mcp_config.get("headers", {}).get("Authorization", "").strip() if authorization: headers["Authorization"] = authorization return headers @@ -146,8 +143,8 @@ class MCPClient: - 让用户在每个会话里重复输入 `Authorization` - 把 `Authorization` 放到工具参数里 - 给 `Authorization` 自动拼接 `Bearer ` 前缀 -- 只在当前 shell 中临时 `export EDUCODER_AUTHORIZATION`,没有写入 OpenClaw 持久化配置 -- 在进程启动后只读取一次 token,导致环境变量更新后无法生效 +- 把 token 写入 OpenClaw `.env` 后要求用户重启才能生效 +- 每个会话都重复要求用户输入 token ## 触发场景 @@ -195,7 +192,7 @@ class MCPClient: - 参数名必须严格匹配工具定义 - 当前查询逻辑还是占位实现,后续会接外部 HTTP 接口 - `course_id` 和 `exercise_id` 当前按字符串传入 -- 客户端每次连接 MCP 服务时,请从环境变量 `EDUCODER_AUTHORIZATION` 读取 token 原文,并在 transport headers 中传 `Authorization`,不要添加 `Bearer ` 前缀 +- 客户端每次连接 MCP 服务时,请从 OpenClaw MCP 配置读取 token 原文,并在 transport headers 中传 `Authorization`,不要添加 `Bearer ` 前缀 --- @@ -306,16 +303,17 @@ class MCPClient: ## 客户端调用示例 ```python -import os - import httpx from mcp import ClientSession from mcp.client.streamable_http import streamable_http_client -async def main(): +async def main(openclaw_mcp_config): + mcp_config = openclaw_mcp_config.get("educoder-mcp") + token = mcp_config.get("headers", {}).get("Authorization", "").strip() + headers = { - "Authorization": os.getenv("EDUCODER_AUTHORIZATION", "").strip() + "Authorization": token } async with httpx.AsyncClient(headers=headers) as http_client: @@ -334,8 +332,8 @@ async def main(): - 当前服务使用 `streamable-http`,不是 SSE - 如果客户端使用了 `sse_client`,将无法正常调用当前服务 -- `Authorization` 应从环境变量 `EDUCODER_AUTHORIZATION` 读取 token 原文,通过 MCP 连接层 headers 传入,不放在工具参数里,也不要添加 `Bearer ` 前缀 -- 调用接口返回 `401 Unauthorized` 时,视为 token 过期或无效,应要求用户重新输入 token,并同步更新 OpenClaw 持久化配置和环境变量 `EDUCODER_AUTHORIZATION` -- 新开会话读不到 token 时,通常是 token 只做了临时 `export`,没有写入 OpenClaw 持久化配置;需要先从持久化配置恢复到环境变量 +- `Authorization` 应从 OpenClaw MCP 配置读取 token 原文,通过 MCP 连接层 headers 传入,不放在工具参数里,也不要添加 `Bearer ` 前缀 +- 调用接口返回 `401 Unauthorized` 时,视为 token 过期或无效,应要求用户重新输入 token,并更新 OpenClaw MCP 配置文件中的 `headers.Authorization` +- 新开会话直接从 MCP 配置读取 `headers.Authorization` - 当前工具返回空 `data` 数组属于预期行为,因为外部接口尚未接入 - 后续新增或调整工具时,需要同步更新本文件