diff --git a/SKILL.md b/SKILL.md index fbae190..a227fe4 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,10 +1,11 @@ --- -name: educoder-mcp +name: educoder-skill description: 提供 Educoder 用户与课堂相关查询能力,当前支持用户基本信息、课堂列表、课堂作业列表、作业学生列表、课堂考试列表、考试学生列表查询。 --- ## MCP 服务地址 +- 线上地址:`http://47.98.32.66:48000/mcp` - 本地开发:`http://localhost:8001/mcp` > 使用 `streamable-http` 模式连接,不使用 SSE。 @@ -12,33 +13,84 @@ description: 提供 Educoder 用户与课堂相关查询能力,当前支持用 ## 接入方式 - 客户端应使用 `streamable-http` MCP 客户端 +- 使用 OpenClaw 安装本 skill 时,MCP 服务地址直接填写线上地址:`http://47.98.32.66:48000/mcp` - 不要使用 `sse_client` -- 服务端口默认是 `8001` +- 本地开发端口默认是 `8001`,线上端口使用 `48000` - MCP 路径默认是 `/mcp` +## 安装完成提示 + +安装完成后,面向用户只介绍当前 skill 提供的功能,不展示 MCP 地址、配置文件路径、token 保存方式、headers 规则等接入细节。 + +推荐展示文案: + +```text +Educoder skill 已安装完成。当前支持查询: +- 当前用户基本信息 +- 当前用户的课堂列表 +- 课堂中的作业列表 +- 某次作业下的学生列表 +- 课堂中的考试列表 +- 某场考试下的学生列表 +``` + + ## 前置配置 -接入 OpenClaw 时,需要让用户提供 `Authorization`,并在 MCP 客户端建立连接时放到请求头里。 +接入 OpenClaw 时,多个会话使用同一个 `Authorization` token。第一次安装 skill 时,把 MCP 服务地址和 token 一起写到固定的 OpenClaw MCP 配置文件 `.openclaw/educoder-skill/.mcp.json` 中;后续调用 MCP 时,也只从 `.openclaw/educoder-skill/.mcp.json` 读取 token 并放到请求头里。 + +### Authorization 配置 -### 单用户本地调试 +安装 skill 时要求把用户输入 Educoder `Authorization` token 写入 MCP 配置文件 `.openclaw/educoder-skill/.mcp.json`。 -本地单用户调试时,可以临时使用环境变量保存: +配置文件路径必须固定为: -```bash -export EDUCODER_AUTHORIZATION="xxxxx" +```text +.openclaw/educoder-skill/.mcp.json ``` -> 这种方式只适合本地单用户调试,不适合多用户线上场景。 +不要写入 `.openclaw/mcp.json` 或 `.openclaw/config/mcp.json`,否则新开会话时可能读取不到配置。 -MCP 客户端连接时应注入: +MCP 配置示例: + +```json +{ + "educoder-mcp": { + "url": "http://47.98.32.66:48000/mcp", + "transport": "streamable-http", + "headers": { + "Authorization": "xxxxx" + } + } +} +``` + +> `Authorization` 的值直接写 token 原文,不要写成 `Bearer xxxxx`。不要把 token 放到工具参数里。 + +后续调用 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 } ``` -然后在创建 MCP 连接时透传: +`Authorization` 的 header 值直接传 token 原文,不要拼接 `Bearer ` 前缀。例如: + +```http +Authorization: xxxxx +``` + +不要传成: + +```http +Authorization: Bearer xxxxx +``` + +创建 MCP 连接时透传: ```python async with streamable_http_client(self.server_url, headers=headers or None) as (read, write, _): @@ -47,58 +99,50 @@ async with streamable_http_client(self.server_url, headers=headers or None) as ( > `Authorization` 走 MCP 连接层请求头,不放在工具参数里。 -### 多用户隔离 - -多用户场景下,不要把 `Authorization` 放在服务进程级环境变量里,否则所有用户会共用同一份认证信息。 +### 多会话共享 Token -正确做法是: +多个会话共用 `.openclaw/educoder-skill/.mcp.json` 中的同一个 `Authorization` token。安装后不需要每个会话重复输入 token;每次调用 MCP 时读取配置里的 `headers.Authorization` 即可。 -1. 让每个用户单独输入自己的 `Authorization` -2. 调用端把这个值绑定到当前用户会话 -3. 创建当前用户专属的 MCPClient 实例 -4. 在 MCP 建连时,把当前用户自己的 `Authorization` 放到请求头里 +### 401 Token 过期处理 -示例: +如果调用 MCP 工具或后端接口时返回 `401 Unauthorized`,说明当前 token 已过期或无效。 -```python -client = MCPClient( - server_url=tool_server_url, - authorization=current_user_authorization, -) -``` +处理流程: -然后在建连时动态透传: +1. 停止继续使用当前 token 重试 +2. 提示用户重新输入新的 `Authorization` +3. 将用户输入的新 token 写入 `.openclaw/educoder-skill/.mcp.json` +4. 重新创建 MCP 连接,并从 MCP 配置读取最新 token +5. 使用新 token 重新发起本次工具调用 -```python -headers = {} -if self.authorization: - headers["Authorization"] = self.authorization +示例提示: -async with streamable_http_client(self.server_url, headers=headers or None) as (read, write, _): - yield read, write +```text +当前 Educoder 授权已过期,请重新输入 Authorization token。 ``` -> 核心原则:`Authorization` 必须按用户动态传递,不能做成所有人共享的全局配置。 +> 更新 token 后必须重新建立 MCP 连接,不能复用已经携带旧 headers 的连接。 ### OpenClaw 接入建议 如果使用 OpenClaw,推荐按下面的方式接入: -1. 用户在界面上输入自己的 `Authorization` -2. OpenClaw 后端把该值保存到当前用户自己的会话上下文 -3. 当前用户发起工具调用时,后端读取这个用户自己的 `Authorization` -4. 用这个值创建当前用户专属的 MCPClient -5. MCPClient 建连时把 `Authorization` 放进 MCP headers +1. 安装 skill 时,MCP 服务地址填写:`http://47.98.32.66:48000/mcp` +2. 安装 skill 时,要求用户输入 Educoder `Authorization` token +3. OpenClaw 将 MCP 地址和 token 写入 `.openclaw/educoder-skill/.mcp.json`,其中 token 放到 `headers.Authorization` +4. 当前用户发起工具调用时,后端创建 MCPClient +5. MCPClient 建连时从 MCP 配置读取 token +6. MCPClient 把读取到的 token 原文放进 MCP headers,不要添加 `Bearer ` 前缀 +7. 如果调用返回 `401 Unauthorized`,提示用户重新输入 token,更新 MCP 配置后重新建连并重试 参考流程: ```python -current_user_authorization = session_store.get(current_user_id, "educoder_authorization") +tool_server_url = "http://47.98.32.66:48000/mcp" -client = MCPClient( - server_url=tool_server_url, - authorization=current_user_authorization, -) +mcp_config = openclaw_mcp_config.get("educoder-mcp") + +client = MCPClient(mcp_config=mcp_config) tool_result = await client.call_tool(tool_name, tool_arguments) ``` @@ -107,22 +151,25 @@ MCPClient 示例: ```python class MCPClient: - def __init__(self, server_url: str, authorization: str | None = None): - self.server_url = server_url - self.authorization = authorization + 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 = {} - if self.authorization: - headers["Authorization"] = self.authorization + authorization = self.mcp_config.get("headers", {}).get("Authorization", "").strip() + if authorization: + headers["Authorization"] = authorization return headers ``` 不推荐的做法: -- 把 `Authorization` 写进服务端 `.env` -- 把 `Authorization` 写成所有用户共享的全局变量 -- 让多个用户复用同一个 MCPClient 实例 +- 让用户在每个会话里重复输入 `Authorization` +- 把 `Authorization` 放到工具参数里 +- 给 `Authorization` 自动拼接 `Bearer ` 前缀 +- 把 token 写入 OpenClaw `.env` 后要求用户重启才能生效 +- 每个会话都重复要求用户输入 token ## 触发场景 @@ -170,7 +217,7 @@ class MCPClient: - 参数名必须严格匹配工具定义 - 当前查询逻辑还是占位实现,后续会接外部 HTTP 接口 - `course_id` 和 `exercise_id` 当前按字符串传入 -- 客户端连接 MCP 服务时,请在 transport headers 中传 `Authorization` +- 客户端每次连接 MCP 服务时,请从 OpenClaw MCP 配置读取 token 原文,并在 transport headers 中传 `Authorization`,不要添加 `Bearer ` 前缀 --- @@ -286,9 +333,16 @@ from mcp import ClientSession from mcp.client.streamable_http import streamable_http_client -async def main(): - async with httpx.AsyncClient(headers={"Authorization": "demo-token"}) as http_client: - async with streamable_http_client("http://127.0.0.1:8001/mcp", http_client=http_client) as (read, write, _): +async def main(openclaw_mcp_config): + mcp_config = openclaw_mcp_config.get("educoder-mcp") + token = mcp_config.get("headers", {}).get("Authorization", "").strip() + + headers = { + "Authorization": token + } + + async with httpx.AsyncClient(headers=headers) as http_client: + async with streamable_http_client("http://47.98.32.66:48000/mcp", http_client=http_client) as (read, write, _): async with ClientSession(read, write) as session: await session.initialize() @@ -303,6 +357,9 @@ async def main(): - 当前服务使用 `streamable-http`,不是 SSE - 如果客户端使用了 `sse_client`,将无法正常调用当前服务 -- `Authorization` 应通过 MCP 连接层 headers 传入,不放在工具参数里 +- `Authorization` 应从 OpenClaw MCP 配置读取 token 原文,通过 MCP 连接层 headers 传入,不放在工具参数里,也不要添加 `Bearer ` 前缀 +- 调用接口返回 `401 Unauthorized` 时,视为 token 过期或无效,应要求用户重新输入 token,并更新 `.openclaw/educoder-skill/.mcp.json` 中的 `headers.Authorization` +- 新开会话直接从 `.openclaw/educoder-skill/.mcp.json` 读取 `headers.Authorization` +- MCP 配置文件路径固定为 `.openclaw/educoder-skill/.mcp.json`,不要写入或读取 `.openclaw/mcp.json`、`.openclaw/config/mcp.json` - 当前工具返回空 `data` 数组属于预期行为,因为外部接口尚未接入 -- 后续新增或调整工具时,需要同步更新本文件 +- 后续新增或调整工具时,需要同步更新本文件 \ No newline at end of file