更新 'SKILL.md'

main
educoder 2 weeks ago
parent 53edbfc2b9
commit aee2b08f4a

@ -20,19 +20,17 @@ description: 提供 Educoder 用户与课堂相关查询能力,当前支持用
## 前置配置 ## 前置配置
接入 OpenClaw 时,需要让用户提供 `Authorization`,并在 MCP 客户端建立连接时放到请求头里。 接入 OpenClaw 时,多个会话使用同一个 `Authorization` token。调用端每次建立 MCP 连接时,都从环境变量 `EDUCODER_AUTHORIZATION` 读取 token放到请求头里。
### 单用户本地调试 ### Authorization 配置
本地单用户调试时,可以临时使用环境变量保存 在运行 OpenClaw 或 MCP 客户端的进程环境中配置
```bash ```bash
export EDUCODER_AUTHORIZATION="xxxxx" export EDUCODER_AUTHORIZATION="xxxxx"
``` ```
> 这种方式只适合本地单用户调试,不适合多用户线上场景。 MCP 客户端每次连接时应读取环境变量并注入:
MCP 客户端连接时应注入:
```python ```python
headers = { headers = {
@ -49,59 +47,63 @@ async with streamable_http_client(self.server_url, headers=headers or None) as (
> `Authorization` 走 MCP 连接层请求头,不放在工具参数里。 > `Authorization` 走 MCP 连接层请求头,不放在工具参数里。
### 多用户隔离 ### 多会话共享 Token
用户场景下,不要把 `Authorization` 放在服务进程级环境变量里,否则所有用户会共用同一份认证信息 个会话共用同一个 `EDUCODER_AUTHORIZATION`。不要让用户在每个会话里单独输入 token也不要把 token 放到工具参数里
正确做法是: 正确做法是:
1. 让每个用户单独输入自己的 `Authorization` 1. 在 OpenClaw 运行环境中配置 `EDUCODER_AUTHORIZATION`
2. 调用端把这个值绑定到当前用户会话 2. 每次创建 MCPClient 或建立 MCP 连接时,从 `os.getenv("EDUCODER_AUTHORIZATION")` 读取最新值
3. 创建当前用户专属的 MCPClient 实例 3. 把读取到的值放进 MCP transport headers 的 `Authorization`
4. 在 MCP 建连时,把当前用户自己的 `Authorization` 放到请求头里 4. 多个会话可以分别创建 MCPClient 实例,但认证 token 都来自同一个环境变量
示例: ### 401 Token 过期处理
```python 如果调用 MCP 工具或后端接口时返回 `401 Unauthorized`,说明当前 `EDUCODER_AUTHORIZATION` 已过期或无效。
client = MCPClient(
server_url=tool_server_url,
authorization=current_user_authorization,
)
```
然后在建连时动态透传 处理流程:
```python 1. 停止继续使用当前 token 重试
headers = {} 2. 提示用户重新输入新的 `Authorization`
if self.authorization: 3. 将用户输入的新 token 更新到环境变量 `EDUCODER_AUTHORIZATION`
headers["Authorization"] = self.authorization 4. 重新创建 MCP 连接,并从环境变量读取最新 token
5. 使用新 token 重新发起本次工具调用
async with streamable_http_client(self.server_url, headers=headers or None) as (read, write, _): 示例提示:
yield read, write
```text
当前 Educoder 授权已过期,请重新输入 Authorization token。
```
更新环境变量示例:
```bash
export EDUCODER_AUTHORIZATION="new-token"
``` ```
> 核心原则:`Authorization` 必须按用户动态传递,不能做成所有人共享的全局配置。 > 更新 token 后必须重新建立 MCP 连接,不能复用已经携带旧 headers 的连接
### OpenClaw 接入建议 ### OpenClaw 接入建议
如果使用 OpenClaw推荐按下面的方式接入 如果使用 OpenClaw推荐按下面的方式接入
1. 安装 skill 时MCP 服务地址填写:`http://47.98.32.66:48000/mcp` 1. 安装 skill 时MCP 服务地址填写:`http://47.98.32.66:48000/mcp`
2. 用户在界面上输入自己的 `Authorization` 2. 在 OpenClaw 运行环境中配置 `EDUCODER_AUTHORIZATION`
3. OpenClaw 后端把该值保存到当前用户自己的会话上下文 3. 当前用户发起工具调用时,后端创建 MCPClient
4. 当前用户发起工具调用时,后端读取这个用户自己的 `Authorization` 4. MCPClient 建连时从环境变量读取 `EDUCODER_AUTHORIZATION`
5. 用这个值创建当前用户专属的 MCPClient 5. MCPClient 把读取到的值放进 MCP headers
6. MCPClient 建连时把 `Authorization` 放进 MCP headers 6. 如果调用返回 `401 Unauthorized`,提示用户重新输入 token更新 `EDUCODER_AUTHORIZATION` 后重新建连并重试
参考流程: 参考流程:
```python ```python
import os
tool_server_url = "http://47.98.32.66:48000/mcp" tool_server_url = "http://47.98.32.66:48000/mcp"
current_user_authorization = session_store.get(current_user_id, "educoder_authorization")
client = MCPClient( client = MCPClient(
server_url=tool_server_url, server_url=tool_server_url,
authorization=current_user_authorization,
) )
tool_result = await client.call_tool(tool_name, tool_arguments) tool_result = await client.call_tool(tool_name, tool_arguments)
@ -111,22 +113,22 @@ MCPClient 示例:
```python ```python
class MCPClient: class MCPClient:
def __init__(self, server_url: str, authorization: str | None = None): def __init__(self, server_url: str):
self.server_url = server_url self.server_url = server_url
self.authorization = authorization
def _build_headers(self) -> dict[str, str]: def _build_headers(self) -> dict[str, str]:
headers = {} headers = {}
if self.authorization: authorization = os.getenv("EDUCODER_AUTHORIZATION", "").strip()
headers["Authorization"] = self.authorization if authorization:
headers["Authorization"] = authorization
return headers return headers
``` ```
不推荐的做法: 不推荐的做法:
- `Authorization` 写进服务端 `.env` - 让用户在每个会话里重复输入 `Authorization`
- 把 `Authorization` 写成所有用户共享的全局变量 - 把 `Authorization` 放到工具参数里
- 让多个用户复用同一个 MCPClient 实例 - 在进程启动后只读取一次 token导致环境变量更新后无法生效
## 触发场景 ## 触发场景
@ -174,7 +176,7 @@ class MCPClient:
- 参数名必须严格匹配工具定义 - 参数名必须严格匹配工具定义
- 当前查询逻辑还是占位实现,后续会接外部 HTTP 接口 - 当前查询逻辑还是占位实现,后续会接外部 HTTP 接口
- `course_id``exercise_id` 当前按字符串传入 - `course_id``exercise_id` 当前按字符串传入
- 客户端连接 MCP 服务时,请在 transport headers 中传 `Authorization` - 客户端每次连接 MCP 服务时,请从环境变量 `EDUCODER_AUTHORIZATION` 读取 token在 transport headers 中传 `Authorization`
--- ---
@ -285,13 +287,19 @@ class MCPClient:
## 客户端调用示例 ## 客户端调用示例
```python ```python
import os
import httpx import httpx
from mcp import ClientSession from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client from mcp.client.streamable_http import streamable_http_client
async def main(): async def main():
async with httpx.AsyncClient(headers={"Authorization": "demo-token"}) as http_client: headers = {
"Authorization": os.getenv("EDUCODER_AUTHORIZATION", "").strip()
}
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 streamable_http_client("http://47.98.32.66:48000/mcp", http_client=http_client) as (read, write, _):
async with ClientSession(read, write) as session: async with ClientSession(read, write) as session:
await session.initialize() await session.initialize()
@ -307,6 +315,7 @@ async def main():
- 当前服务使用 `streamable-http`,不是 SSE - 当前服务使用 `streamable-http`,不是 SSE
- 如果客户端使用了 `sse_client`,将无法正常调用当前服务 - 如果客户端使用了 `sse_client`,将无法正常调用当前服务
- `Authorization` 应通过 MCP 连接层 headers 传入,不放在工具参数里 - `Authorization` 应从环境变量 `EDUCODER_AUTHORIZATION` 读取,通过 MCP 连接层 headers 传入,不放在工具参数里
- 调用接口返回 `401 Unauthorized` 时,视为 token 过期或无效,应要求用户重新输入 token并更新到环境变量 `EDUCODER_AUTHORIZATION`
- 当前工具返回空 `data` 数组属于预期行为,因为外部接口尚未接入 - 当前工具返回空 `data` 数组属于预期行为,因为外部接口尚未接入
- 后续新增或调整工具时,需要同步更新本文件 - 后续新增或调整工具时,需要同步更新本文件

Loading…
Cancel
Save