发布:学生会策划部工作包(Skills + MCP)
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Tencent
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,246 @@
|
||||
---
|
||||
name: tencent-survey
|
||||
description: 腾讯问卷(wj.qq.com)- 在线问卷调查平台。涉及「问卷」「调查」「表单」「投票」「考试」「测评」「wj.qq.com」等操作时优先使用。支持能力:(1) 获取问卷详情(标题、设置、页面、题目、选项完整结构 + 纯文本 DSL + 自定义逻辑)(2) 使用纯文本创建问卷(text 必填,支持指定场景/指定项目)(3) 更新问卷中的单个题目(DSL 格式)(4) 获取问卷回答列表(支持游标分页)(5) 更新问卷自定义逻辑(条件显示/隐藏、跳转、替换、随机排序等)。支持场景:调查(1)、考试(3)、测评(6)、投票(8)。
|
||||
description_zh: "腾讯问卷操作(创建、修改、逻辑设置、统计)"
|
||||
description_en: "Tencent Survey operations (create, edit, logic settings, view statistical analysis of various surveys)\""
|
||||
version: 1.1.2
|
||||
homepage: https://wj.qq.com
|
||||
metadata:
|
||||
openclaw:
|
||||
primaryEnv: TENCENT_SURVEY_TOKEN
|
||||
category: tencent
|
||||
tencentTokenMode: custom
|
||||
emoji: "\U0001F4CB"
|
||||
display_name: "腾讯问卷"
|
||||
display_name_en: "Tencent Survey"
|
||||
visibility: "public"
|
||||
---
|
||||
|
||||
# SKILL 更新
|
||||
|
||||
每天使用 SKILL 前进行一次更新检查,按照如下的步骤执行:
|
||||
|
||||
1. **查看当前版本version**:读取本文件顶部 frontmatter 中的 `version` 字段;格式为 MAJOR.MINOR.PATCH
|
||||
2. **查看最新版本latest**:通过命令获取最新版本信息`latest`,输入参数 `current_version` 为上一步获取的当前版本
|
||||
```bash
|
||||
mcporter call tencent-survey "check_skill_update" --args '{"current_version": "当前版本"}'
|
||||
```
|
||||
JSON 格式数据返回,返回参数示例:
|
||||
- `has_update`: 是否有更新(true/false)
|
||||
- `latest`: 最新版本号,格式为 MAJOR.MINOR.PATCH
|
||||
- `current_version`: 当前传入的版本号
|
||||
- `release_note`: 最新版本发布说明
|
||||
- `instruction`: 更新指令
|
||||
|
||||
3. **更新版本**:如果 `has_update` 为 true(即当前版本`current_version`低于最新版本`latest`),则遵循 `instruction` 指令进行更新,或提示用户更新
|
||||
|
||||
# 腾讯问卷 MCP 使用指南
|
||||
|
||||
腾讯问卷 MCP 提供问卷查询、创建、编辑与回答查看能力,让 AI 助手可以直接操作问卷系统。
|
||||
|
||||
## 触发场景
|
||||
|
||||
### 明确触发
|
||||
|
||||
以下情况应直接激活本 skill:
|
||||
- 用户提到「问卷」「调查」「表单」「投票」「考试」「测评」等关键词
|
||||
- 用户提供了 `wj.qq.com` 链接
|
||||
- 用户说「帮我做个调查」「创建一个投票」等
|
||||
|
||||
### 模糊场景
|
||||
|
||||
| 用户表述 | 判断方式 |
|
||||
|---------|---------|
|
||||
| 「帮我做个投票」 | 直接使用本 skill,scene=8 |
|
||||
| 「做个考试」 | 直接使用本 skill,scene=3 |
|
||||
| 「做个测评」 | 直接使用本 skill,scene=6 |
|
||||
| 「收集一下大家的意见」 | 直接使用本 skill,scene=1(调查) |
|
||||
| 「我有个问卷链接…」 | 解析链接提取 survey_id,调用 get_survey |
|
||||
| 「修改问卷的第X题」 | 先 get_survey 获取 question_id,再 update_question |
|
||||
| 「设置跳题逻辑」「设置显示逻辑」 | 先 get_survey 获取题目/选项 ID 和当前 survey_dsl,再 update_logic |
|
||||
| 「选了A就跳到结束」 | 先 get_survey 获取 ID,用 branch 语句调用 update_logic |
|
||||
| 「随机显示题目」「选项随机排序」 | 先 get_survey 获取 ID,用 random show / shuffle 调用 update_logic |
|
||||
| 「看看问卷的回答」 | 调用 list_answers,注意翻页获取全部数据 |
|
||||
| 「问卷收了多少份」 | 调用 list_answers 查看 total 字段 |
|
||||
|
||||
## 配置
|
||||
|
||||
**在本次会话首次调用工具前**,完成一次鉴权检查(完整流程见 `references/auth.md`):
|
||||
|
||||
> `${SKILL_DIR}` 为本 skill 所在目录路径(即 `SKILL.md` 所在目录)。由 AI Agent 框架在加载 skill 时自动注入;如果框架未注入,请替换为 `SKILL.md` 所在目录的绝对路径。
|
||||
|
||||
### 方式一:环境变量传入 Token
|
||||
|
||||
如果已有 Token(环境变量 `TENCENT_SURVEY_TOKEN`),直接完成配置,无需 OAuth 授权:
|
||||
|
||||
```bash
|
||||
TENCENT_SURVEY_TOKEN=xxx bash "${SKILL_DIR}/setup.sh" wj_check_and_start_auth
|
||||
```
|
||||
|
||||
脚本检测到 `TENCENT_SURVEY_TOKEN` 后会自动写入 mcporter 配置,输出 `READY` 即表示就绪。
|
||||
|
||||
### 方式二:OAuth 设备授权
|
||||
|
||||
未设置 `TENCENT_SURVEY_TOKEN` 时,自动进入 OAuth 授权流程:
|
||||
|
||||
1. 执行 `bash "${SKILL_DIR}/setup.sh" wj_check_and_start_auth`
|
||||
2. 输出 `READY` → 鉴权已就绪,直接继续
|
||||
3. 输出 `AUTH_REQUIRED:<url>` → 向用户展示授权链接,然后执行 `bash "${SKILL_DIR}/setup.sh" wj_wait_auth` 等待授权完成
|
||||
4. 输出 `ERROR:*` → 告知用户对应错误
|
||||
|
||||
> 鉴权通过后,**同一会话内后续调用无需重复检查**。仅当工具返回 `invalid_token`、`token expired`、`missing_token` 等鉴权错误时,才需要重新执行上述流程。
|
||||
|
||||
- Token 前缀固定为 `wjpt_`,长度 70 字符
|
||||
- 每个 Token 绑定一个团队,只能操作该团队下的问卷
|
||||
|
||||
## 工具列表与调用方式
|
||||
|
||||
| 工具名称 | 功能说明 | 参考文档 |
|
||||
|---------|---------|---------|
|
||||
| get_survey | 获取指定问卷的详细信息(标题、设置、页面、题目、选项 + 纯文本 DSL) | `references/get_survey.md` |
|
||||
| create_survey | 使用纯文本创建问卷(text 必填,支持指定场景/指定项目) | `references/create_survey.md` |
|
||||
| update_question | 更新问卷中的某一道题目(需先获取 question_id) | `references/update_question.md` |
|
||||
| update_logic | 更新问卷的自定义逻辑设置(条件显示/隐藏、跳转、替换、随机等) | `references/update_logic.md` |
|
||||
| list_answers | 获取问卷的回答列表(支持游标分页) | `references/list_answers.md` |
|
||||
| check_skill_update | 检查 Skill 是否有新版本可更新 | 见上方「SKILL 更新」章节 |
|
||||
|
||||
调用优先级:
|
||||
|
||||
1. **MCP 原生调用**:如果当前 AI Agent 已通过 MCP 协议连接了 tencent-survey 服务(工具列表中可见 `get_survey`、`create_survey`、`update_question`、`update_logic`、`list_answers`),直接调用工具即可
|
||||
2. **mcporter CLI 调用**:如果 AI Agent 不支持 MCP 原生调用,或工具列表中未出现 tencent-survey 工具,通过终端执行 `mcporter call tencent-survey.<tool_name> --args '{...}'`
|
||||
3. **确认工具可用**:使用 `mcporter list tencent-survey` 查看已注册的工具列表和参数 Schema
|
||||
|
||||
> 参考文档中的参数说明应与 MCP 工具 Schema 保持一致。如有冲突,以 `mcporter list tencent-survey` 返回的 Schema 为准。
|
||||
|
||||
## URL 解析规则
|
||||
|
||||
问卷投放链接格式为 `https://wj.qq.com/s2/{survey_id}/{hash}`
|
||||
|
||||
当用户提供链接时,取路径第二段为 `survey_id`:
|
||||
|
||||
| URL 格式 | 提取方式 | 示例 |
|
||||
|----------|---------|------|
|
||||
| `wj.qq.com/s2/{id}/{hash}` | 取路径第二段为 survey_id | `wj.qq.com/s2/292192/abc1` → `292192` |
|
||||
|
||||
> 提取到 `survey_id` 后,调用 `get_survey(survey_id=...)` 获取问卷详情。
|
||||
|
||||
## 数据模型
|
||||
|
||||
```
|
||||
问卷(Survey)
|
||||
├── 基本信息:id, hash, title, scene, state
|
||||
├── 设置:prefix(欢迎语), suffix(结束语), started_at, end_at ...
|
||||
├── 项目:project { id, name }
|
||||
├── 纯文本内容:text(DSL 格式,包含标题和所有题目)
|
||||
├── 自定义逻辑:survey_dsl { code, errors } ← 通过 update_logic 更新
|
||||
├── 页面列表(Pages[])
|
||||
│ └── 题目列表(Questions[])
|
||||
│ ├── 基本属性:id, type, sub_type, title, required
|
||||
│ ├── 选项列表(Options[]):id, text, exclusive
|
||||
│ ├── 量表属性:starBeginNum, starNum
|
||||
│ ├── 矩阵子问题:subTitles[]
|
||||
│ └── 联动层级:levels[], groups[]
|
||||
└── 回答列表(Answers[])← 通过 list_answers 获取
|
||||
├── 基本信息:answer_id, respondent_nickname, started_at, ended_at
|
||||
├── 地理信息:country, province, city
|
||||
└── 回答内容(answer[])
|
||||
└── 页面 → 题目回答 { id, type, text, options, blanks, groups }
|
||||
```
|
||||
|
||||
> 核心嵌套关系:`Survey → Pages[] → Questions[] → Options[]`
|
||||
> 回答嵌套关系:`Answer → answer[] (Pages) → questions[]`
|
||||
|
||||
## 常见工作流
|
||||
|
||||
### 查看问卷详情
|
||||
|
||||
参考文档:`references/get_survey.md`
|
||||
|
||||
1. 执行鉴权检查(见上方「配置」节)
|
||||
2. 从用户提供的链接或 ID 获取 `survey_id`(链接解析见「URL 解析规则」)
|
||||
3. 调用 `get_survey(survey_id=...)` 获取问卷详情
|
||||
4. 递归解析 `pages → questions → options` 嵌套结构
|
||||
5. 向用户展示问卷标题、题目列表等信息
|
||||
|
||||
### 创建问卷
|
||||
|
||||
参考文档:`references/create_survey.md`
|
||||
|
||||
1. 执行鉴权检查(见上方「配置」节)
|
||||
2. 根据用户需求判断 `scene`:调查(1, 默认)、考试(3)、测评(6)、投票(8)
|
||||
3. 按问卷文本语法组织 `text` 内容(语法详见参考文档)
|
||||
4. 如果用户指定了项目,传入 `project_id`
|
||||
5. 调用 `create_survey` 创建问卷
|
||||
6. 从返回结果中取 `survey_id` 和 `hash`,拼接投放链接 `wj.qq.com/s2/{survey_id}/{hash}` 告知用户
|
||||
7. 可选:调用 `get_survey` 确认问卷结构
|
||||
|
||||
### 更新问卷题目
|
||||
|
||||
参考文档:`references/update_question.md`
|
||||
|
||||
1. 执行鉴权检查(见上方「配置」节)
|
||||
2. 调用 `get_survey(survey_id=...)` 获取问卷详情
|
||||
3. 从返回的 `pages → questions` 中找到目标题目的 `id`(格式如 `q-1-xxxx`)
|
||||
4. 参考返回的 `text` 字段了解当前问卷的 DSL 格式
|
||||
5. 按 DSL 语法编写新的题目文本(只写这一道题,不需要问卷标题)
|
||||
6. 调用 `update_question(survey_id=..., question_id=..., text=...)` 更新题目
|
||||
7. 可选:再次调用 `get_survey` 确认更新结果
|
||||
|
||||
### 设置问卷逻辑
|
||||
|
||||
参考文档:`references/update_logic.md`
|
||||
|
||||
1. 执行鉴权检查(见上方「配置」节)
|
||||
2. 调用 `get_survey(survey_id=...)` 获取问卷详情
|
||||
3. 从返回的 `survey_dsl.code` 获取当前自定义逻辑代码(如有)
|
||||
4. 从返回的 `pages → questions → options` 中获取题目 ID 和选项 ID
|
||||
5. 根据用户需求编写 DSL 逻辑代码(ID 用反引号包裹,如 `` `q-1-abcd::o-100-EFGH` ``)
|
||||
6. 如需追加规则,在原有 `survey_dsl.code` 基础上添加新规则
|
||||
7. 调用 `update_logic(survey_id=..., dsl=...)` 更新逻辑(整体覆盖)
|
||||
8. 可选:再次调用 `get_survey` 确认逻辑已生效
|
||||
|
||||
### 查看问卷回答
|
||||
|
||||
参考文档:`references/list_answers.md`
|
||||
|
||||
1. 执行鉴权检查(见上方「配置」节)
|
||||
2. 调用 `list_answers(survey_id=...)` 获取首页回答
|
||||
3. **⚠️ 注意翻页**:如果 `list.length == per_page`,说明可能还有下一页,需要循环翻页:
|
||||
- 将返回的 `last_answer_id` 作为下一次请求的参数
|
||||
- 继续调用 `list_answers(survey_id=..., last_answer_id=...)` 获取下一页
|
||||
- 直到 `list.length < per_page` 表示已到最后一页
|
||||
4. 解析每条回答的 `answer` 字段(嵌套结构:`页面 → 题目回答`)
|
||||
5. 向用户展示回答汇总或详情
|
||||
|
||||
## 注意事项
|
||||
|
||||
- **标题可能含 HTML 标签**:`get_survey` 返回的 `title` 字段可能包含 `<p>`、`<br>` 等标签,展示给用户前需清理
|
||||
- **text 字段(DSL 格式)**:`get_survey` 返回的 `text` 字段是纯文本 DSL 格式的问卷内容,可作为 `update_question` 的参考
|
||||
- **text 参数格式**:`create_survey` 和 `update_question` 的 `text` 为必填,JSON 中换行使用 `\n`,选项不需要字母前缀(写 `满意` 而非 `A. 满意`)
|
||||
- **update_question 需先获取 question_id**:必须先调用 `get_survey` 获取题目列表,不能自行构造 question_id
|
||||
- **update_logic 整体覆盖**:每次调用 `update_logic` 会覆盖所有已有逻辑,追加规则需先获取当前 `survey_dsl.code`
|
||||
- **survey_dsl 字段**:`get_survey` 返回的 `survey_dsl` 包含当前自定义逻辑代码(`code`)和错误信息(`errors`),作为 `update_logic` 的参考
|
||||
- **list_answers 需要翻页**:回答列表使用游标分页,如果回答数量超过 `per_page`(默认 20),必须循环调用直到获取完所有数据
|
||||
- **非幂等的写操作**:`create_survey` 每次调用都会创建新问卷,`update_question` 每次调用都会覆盖原题目
|
||||
|
||||
## 问题定位指南
|
||||
|
||||
### 常见错误码
|
||||
|
||||
| 错误码 | 错误类型 | 解决方案 |
|
||||
|--------|---------|---------|
|
||||
| `missing_token` | 请求未携带 Token | 检查 Authorization Header 或 access_token 参数 |
|
||||
| `invalid_token_prefix` | Token 前缀错误 | 确认使用 `wjpt_` 开头的 Token |
|
||||
| `invalid token` | Token 不存在或已撤销 | 重新创建 Token |
|
||||
| `token expired` | Token 已过期 | 重新授权,详见 `references/auth.md` |
|
||||
| `claim_error` | 问卷不属于当前团队 | 确认问卷 ID 正确且属于当前 Token 绑定的团队 |
|
||||
| `invalid_text_format` | 文本格式错误(create_survey / update_question) | 检查 text DSL 语法 |
|
||||
| `survey_not_editable` | 问卷不可编辑(update_question) | 问卷可能正在回收中,需先暂停 |
|
||||
| `paid_function_trial_no_permission` | 付费功能无权限(如自定义逻辑) | 引导用户前往[腾讯问卷购买页](https://wj.qq.com/pricing)升级为付费版本 |
|
||||
|
||||
### 排查步骤
|
||||
|
||||
1. **检查错误信息**:查看返回的 error 字段,确定错误类型
|
||||
2. **检查请求参数**:确认 `survey_id` 等参数值正确
|
||||
3. **阅读参考文档**:`references/` 目录下包含所有工具的参数说明
|
||||
4. **获取工具列表**:使用 `mcporter list tencent-survey` 确认工具是否可用
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "腾讯问卷",
|
||||
"installedAt": 1787572419124,
|
||||
"source": "marketplace",
|
||||
"version": "1.1.2",
|
||||
"skillId": "skill_2053083197624082432",
|
||||
"examples_zh": [
|
||||
"用纯文本帮我创建一份员工满意度腾讯问卷,包含单选、量表和开放题。",
|
||||
"把这份腾讯问卷的第 5 题改成多选题,并保留原有选项含义。",
|
||||
"为课程反馈问卷设置逻辑:选择\"不满意\"的用户跳转到原因追问题。"
|
||||
],
|
||||
"examples_en": [
|
||||
"Create a Tencent Survey from plain text for employee satisfaction, with single-choice, scale, and open-ended questions.",
|
||||
"Update question 5 in this Tencent Survey into a multiple-choice question while preserving the option meanings.",
|
||||
"Set survey logic so respondents who choose \"Dissatisfied\" in the course feedback form jump to a follow-up reason question."
|
||||
],
|
||||
"description_zh": "腾讯问卷操作(创建、修改、逻辑设置、统计)",
|
||||
"description_en": "Tencent Survey operations (create, edit, logic settings, view statistical analysis of various surveys)\"",
|
||||
"iconSource": "https://openplatform-cdn.codebuddy.cn/public/skills/icons/tencent-survey.png",
|
||||
"iconDownloadedFrom": "https://openplatform-cdn.codebuddy.cn/public/skills/icons/tencent-survey.png",
|
||||
"installedContentHash": "sha256:d727d450647bb8e47cc20c3395f01862e5a8c5f6243a66328734658fa0cdbfb5"
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
# 腾讯问卷鉴权检查
|
||||
|
||||
## 何时需要鉴权
|
||||
|
||||
- **会话首次调用工具前**:执行一次鉴权检查
|
||||
- **鉴权通过后**:同一会话内后续调用**无需重复检查**,直接调用工具即可
|
||||
- **按需重试**:仅当工具调用返回以下鉴权错误时,才需重新执行鉴权流程:
|
||||
- `invalid_token` / `invalid token`
|
||||
- `token expired`
|
||||
- `missing_token`
|
||||
- `invalid_token_prefix`
|
||||
|
||||
腾讯问卷授权流程,**必须按以下步骤执行**:
|
||||
|
||||
## 快速配置:通过环境变量传入 Token
|
||||
|
||||
如果已有 Token,可通过 `TENCENT_SURVEY_TOKEN` 环境变量直接完成配置,跳过 OAuth 授权:
|
||||
|
||||
```bash
|
||||
TENCENT_SURVEY_TOKEN=xxx bash "${SKILL_DIR}/setup.sh" wj_check_and_start_auth
|
||||
```
|
||||
|
||||
| 输出 | 处理方式 |
|
||||
|------|---------|
|
||||
| `READY` | ✅ Token 已写入配置,直接执行用户任务 |
|
||||
| `ERROR:invalid_token_prefix` | Token 格式错误,必须以 `wjpt_` 开头 |
|
||||
| `ERROR:save_token_failed` | Token 写入配置失败 |
|
||||
|
||||
> 设置了 `TENCENT_SURVEY_TOKEN` 时,脚本会优先使用该 Token,不再发起 OAuth 流程。
|
||||
|
||||
## 第一步:检查状态(立即返回)
|
||||
|
||||
未设置 `TENCENT_SURVEY_TOKEN` 时,进入标准 OAuth 设备授权流程:
|
||||
|
||||
```bash
|
||||
bash "${SKILL_DIR}/setup.sh" wj_check_and_start_auth
|
||||
```
|
||||
|
||||
> `${SKILL_DIR}` 为当前 skill 所在目录路径(即 `setup.sh` 所在目录)。
|
||||
|
||||
| 输出 | 处理方式 |
|
||||
|------|---------|
|
||||
| `READY` | ✅ 直接执行用户任务,**无需第二步** |
|
||||
| `NONCE:<nonce>` | 记录 nonce 值,用于展示给用户(在 `AUTH_REQUIRED` 之前输出) |
|
||||
| `AUTH_REQUIRED:<url>` | **立即**向用户展示授权链接(见下方模板),**然后执行第二步** |
|
||||
| `ERROR:*` | 告知用户对应错误 |
|
||||
|
||||
## 第二步:等待授权完成(仅 AUTH_REQUIRED 时执行)
|
||||
|
||||
**展示授权链接后**,立即执行:
|
||||
|
||||
```bash
|
||||
bash "${SKILL_DIR}/setup.sh" wj_wait_auth
|
||||
```
|
||||
|
||||
| 输出 | 处理方式 |
|
||||
|------|---------|
|
||||
| `TOKEN_READY:ok` | ✅ 授权成功,Token 已写入配置,继续执行用户任务 |
|
||||
| `AUTH_TIMEOUT` | 告知用户:「授权超时,请重新发起请求。」 |
|
||||
| `ERROR:*` | 告知用户对应错误,请重新发起请求 |
|
||||
|
||||
## 第三步:人工兜底(前两步都失败的情况)
|
||||
|
||||
🔑 **手动获取 Token**:访问 [https://wj.qq.com/claw](https://wj.qq.com/claw) 登录后创建 Token,再通过环境变量配置:
|
||||
|
||||
```bash
|
||||
TENCENT_SURVEY_TOKEN=<your_token> bash "${SKILL_DIR}/setup.sh" wj_check_and_start_auth
|
||||
```
|
||||
|
||||
或手动执行 mcporter 命令:
|
||||
|
||||
```bash
|
||||
mcporter config add tencent-survey "https://wj.qq.com/api/v2/mcp" \
|
||||
--header "Authorization=Bearer <your_token>" \
|
||||
--transport http \
|
||||
--scope home
|
||||
```
|
||||
|
||||
> Token 以 `wjpt_` 开头,可在 [https://wj.qq.com/oauth/authorize](https://wj.qq.com/oauth/authorize) 登录后创建。
|
||||
|
||||
## 授权链接展示模板
|
||||
|
||||
当第一步输出 `AUTH_REQUIRED:<url>` 时,**立即**向用户展示:
|
||||
|
||||
> 🔑 **需要先完成腾讯问卷授权**
|
||||
>
|
||||
> 请确保在**浏览器**中打开以下链接完成授权:**[点击授权腾讯问卷]({url})**
|
||||
>
|
||||
> 🔑 授权码(nonce):`{nonce}`
|
||||
>
|
||||
> ⚠️ 请使用 **QQ 或微信** 扫码 / 登录授权
|
||||
>
|
||||
> _(授权后将自动继续,无需回复)_
|
||||
|
||||
## 错误说明
|
||||
|
||||
| 错误 | 含义 |
|
||||
|------|------|
|
||||
| `ERROR:mcporter_not_found` | 缺少依赖,请先安装 Node.js |
|
||||
| `ERROR:invalid_token_prefix` | `TENCENT_SURVEY_TOKEN` 格式错误,必须以 `wjpt_` 开头 |
|
||||
| `ERROR:empty_token` | 授权异常,Token 为空 |
|
||||
| `ERROR:save_token_failed` | Token 写入配置失败 |
|
||||
| `AUTH_TIMEOUT` | 用户未在时限内完成授权 |
|
||||
@@ -0,0 +1,406 @@
|
||||
# create_survey 工具参考
|
||||
|
||||
## 概述
|
||||
|
||||
使用纯文本创建问卷。系统会自动将文本解析为问卷结构。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `text` | string | **是** | 问卷内容文本,换行使用 `\n` 代替(具体语法见下文) |
|
||||
| `scene` | number | 否 | 问卷场景,默认为 1(调查),见下方场景枚举 |
|
||||
| `project_id` | number | 否 | 项目 ID,将问卷创建到指定项目下 |
|
||||
|
||||
> **注意**:`text` 为必填参数。创建空白问卷时至少需要传入问卷标题,如 `text="新建问卷"`。
|
||||
|
||||
## 返回值
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"survey_id": 716128,
|
||||
"hash": "859f"
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `survey_id` | number | 新创建的问卷 ID |
|
||||
| `hash` | string | 问卷 hash,用于拼接投放链接 |
|
||||
|
||||
> **投放链接拼接**:`https://wj.qq.com/s2/{survey_id}/{hash}`
|
||||
|
||||
### 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": "InvalidArgument",
|
||||
"error": {
|
||||
"type": "invalid_text_format"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 场景枚举
|
||||
|
||||
| scene | 场景 | 说明 |
|
||||
|-------|------|------|
|
||||
| **1** | **调查** | **默认值**,通用问卷调查 |
|
||||
| 3 | 考试 | 带评分的考试问卷 |
|
||||
| 6 | 测评 | 测评类问卷 |
|
||||
| 8 | 投票 | 投票类问卷 |
|
||||
|
||||
## text 文本语法详解
|
||||
|
||||
`text` 参数使用纯文本描述问卷结构,系统会自动解析为对应的问卷题型。
|
||||
|
||||
> **重要**:在 JSON 参数中,所有换行必须使用 `\n` 代替。
|
||||
>
|
||||
> 📖 语法详细参考:
|
||||
> - [内容编辑概述](https://wj.qq.com/docs/survey-dsl/content/)
|
||||
> - [语法说明](https://wj.qq.com/docs/survey-dsl/content/grammar)
|
||||
> - [普通场景语法](https://wj.qq.com/docs/survey-dsl/content/scene-default)
|
||||
> - [考试场景语法](https://wj.qq.com/docs/survey-dsl/content/scene-exam)
|
||||
> - [测评场景语法](https://wj.qq.com/docs/survey-dsl/content/scene-assessment)
|
||||
|
||||
### 基础结构
|
||||
|
||||
```
|
||||
问卷标题
|
||||
|
||||
问卷引导语(可选)
|
||||
|
||||
题目标题[题型](描述)
|
||||
选项/内容
|
||||
```
|
||||
|
||||
- 第一行为**问卷标题**
|
||||
- 标题后可跟**引导语**(可选)
|
||||
- 使用 `=== 分页 ===` 插入分页符
|
||||
- 题目之间用空行分隔
|
||||
|
||||
### 题型语法列表
|
||||
|
||||
| 题型 | 语法 | 说明 |
|
||||
|------|------|------|
|
||||
| 单选题 | `标题[单选题](描述)\n选项A\n选项B` | 选项另起一行,每行一个 |
|
||||
| 多选题 | `标题[多选题](描述)\n选项A\n选项B` | 选项另起一行 |
|
||||
| 下拉题 | `标题[下拉题](描述)\n选项A\n选项B` | 选项另起一行 |
|
||||
| 排序题 | `标题[排序题](描述)\n选项A\n选项B` | 选项另起一行 |
|
||||
| 单行文本题 | `标题[单行文本题](描述)` | 无需选项 |
|
||||
| 多行文本题 | `标题[多行文本题](描述)` | 无需选项 |
|
||||
| 多项填空题 | `标题[多项填空题](描述)\n填空1:____` | 使用 `____` 指定填空位置 |
|
||||
| 量表题 | `标题[量表题](描述)\n1~5` | 使用数字范围指定量表 |
|
||||
| 日期时间题 | `标题[日期时间题](描述)` | 无需选项 |
|
||||
| 地理位置题 | `标题[地理位置题](描述)` | 无需选项 |
|
||||
| 附件题 | `标题[附件题](描述)` | 无需选项 |
|
||||
| 手写签名题 | `标题[手写签名题](描述)` | 无需选项 |
|
||||
| 段落说明 | `描述内容[段落说明]` | 用于纯文本段落说明(也称 `[文本描述题]`) |
|
||||
| 矩阵单选题 | `标题[矩阵单选题](描述)\n选项A 选项B\n子问题1\n子问题2` | 选项空格分隔,子问题另起一行 |
|
||||
| 矩阵多选题 | `标题[矩阵多选题](描述)\n选项A 选项B\n子问题1\n子问题2` | 同上 |
|
||||
| 矩阵量表题 | `标题[矩阵量表题](描述)\n1~5\n子问题1\n子问题2` | 量表范围在前,子问题在后 |
|
||||
| 联动题 | `标题[联动题](描述)\n第一层 第二层\n答案A+子答案A1+子答案A2\n答案B+子答案B1` | 层级名空格分隔,答案用 `+` 连接 |
|
||||
|
||||
> **提示**:`(描述)` 部分为可选的题目描述/说明文字。`[单选题]` 为默认题型,可以省略。
|
||||
|
||||
### 题目结构
|
||||
|
||||
每道题目的完整结构为:
|
||||
|
||||
```
|
||||
标题[题型][设置](描述)
|
||||
```
|
||||
|
||||
各部分顺序固定,不可调换。其中 `[设置]` 和 `(描述)` 都是可选的。
|
||||
|
||||
### 题目设置
|
||||
|
||||
支持在题型后追加设置标记:
|
||||
|
||||
| 设置 | 语法 | 说明 |
|
||||
|------|------|------|
|
||||
| 必答 | `[必答]` | 该题必须作答 |
|
||||
| 选答 | `[选答]` | 该题可以跳过 |
|
||||
|
||||
示例:`您的姓名[单行文本题][必答](请填写真实姓名)`
|
||||
|
||||
### 考试场景专用设置
|
||||
|
||||
在考试场景(`scene=3`)中,题目可追加答案、分值和评分机制设置:
|
||||
|
||||
| 设置 | 语法 | 说明 |
|
||||
|------|------|------|
|
||||
| 答案 | `[答案:A、B]` | 正确答案,选项索引从 A 开始 |
|
||||
| 填空答案 | `[答案:1.答案1、2.答案2]` | 各填空的正确答案 |
|
||||
| 分值 | `[分数:5]` | 该题总分值 |
|
||||
| 多空分值 | `[分数:2、3]` | 各空/各选项的分值 |
|
||||
| 全部正确得分 | `[全部]` | 完全匹配答案才得分 |
|
||||
| 部分正确得分 | `[部分]` | 部分匹配也可得分(多选题、不定项选择题) |
|
||||
| 按空得分 | `[按空]` | 按填空分别计分(多项填空题) |
|
||||
| 人工评分 | `[人工]` | 需人工阅卷评分(所有题型均可) |
|
||||
|
||||
> **考试题型**:考试场景额外支持 `[判断题]`、`[不定项选择题]`、`[问答题]` 三种题型。
|
||||
|
||||
### 测评场景专用题型
|
||||
|
||||
在测评场景(`scene=6`)中,选择题使用专用题型标记:
|
||||
|
||||
| 题型 | 语法 | 说明 |
|
||||
|------|------|------|
|
||||
| 测评单选题 | `[测评单选题]` | 替代普通场景的 `[单选题]` |
|
||||
| 测评多选题 | `[测评多选题]` | 替代普通场景的 `[多选题]` |
|
||||
|
||||
> 测评场景也支持 `[量表题]`、`[矩阵单选题]`、`[矩阵量表题]`、`[单行文本题]`、`[多行文本题]`、`[多项填空题]`、`[文本描述题]`。
|
||||
|
||||
### 富文本语法
|
||||
|
||||
在题目标题、描述和选项中支持使用以下富文本语法:
|
||||
|
||||
| 类型 | 语法 | 说明 |
|
||||
|------|------|------|
|
||||
| 高亮 | `**高亮文本**` | 文本加粗/高亮显示 |
|
||||
| 链接 | `[链接文本](https://example.com)` | 插入超链接 |
|
||||
| 图片 | `{宽度, 高度}` | 插入图片,`{宽度, 高度}` 必填,值为数字或 `auto` |
|
||||
| 视频 | `!video(视频地址)` | 插入视频,仅支持腾讯视频、哔哩哔哩、优酷 |
|
||||
|
||||
### 完整示例(普通调查场景)
|
||||
|
||||
以下示例覆盖了普通调查场景(`scene=1`,默认)中的所有题型:
|
||||
|
||||
```
|
||||
员工满意度调查
|
||||
|
||||
为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们。
|
||||
|
||||
1. 您在公司工作了多久?[单选题]
|
||||
1年以下
|
||||
1-3年
|
||||
3-5年
|
||||
5年以上
|
||||
|
||||
2. 您对以下哪些方面比较满意?[多选题]
|
||||
工作环境
|
||||
薪资福利
|
||||
团队氛围
|
||||
职业发展
|
||||
|
||||
3. 请选择您的部门[下拉题]
|
||||
技术部
|
||||
产品部
|
||||
市场部
|
||||
人力资源部
|
||||
|
||||
4. 请对以下福利按重要程度排序[排序题]
|
||||
薪资待遇
|
||||
年假天数
|
||||
培训机会
|
||||
弹性工作
|
||||
|
||||
5. 请对整体工作满意度打分[量表题](5分表示非常满意,1分表示非常不满意)
|
||||
1~5
|
||||
|
||||
6. 您的姓名[单行文本题]
|
||||
|
||||
7. 您有什么建议或意见?[多行文本题]
|
||||
|
||||
8. 请填写以下信息[多项填空题]
|
||||
姓名:____ 工号:____
|
||||
|
||||
9. 请选择您的入职日期[日期时间题]
|
||||
|
||||
10. 请选择您的办公地点[地理位置题]
|
||||
|
||||
11. 请上传相关材料[附件题]
|
||||
|
||||
12. 请签名确认[手写签名题]
|
||||
|
||||
本问卷到此结束,感谢您的参与![段落说明]
|
||||
|
||||
=== 分页 ===
|
||||
|
||||
13. 请对各部门的协作效率打分[矩阵单选题]
|
||||
非常好 较好 一般 较差
|
||||
技术部
|
||||
产品部
|
||||
市场部
|
||||
|
||||
14. 以下哪些部门您有过合作经历?[矩阵多选题]
|
||||
有合作 有交流 无接触
|
||||
技术部
|
||||
产品部
|
||||
市场部
|
||||
|
||||
15. 请为各方面打分[矩阵量表题](1分最低,5分最高)
|
||||
1~5
|
||||
工作环境
|
||||
薪资福利
|
||||
团队氛围
|
||||
|
||||
16. 请选择您的所在区域[联动题]
|
||||
省份 城市
|
||||
广东省+广州市+深圳市+东莞市
|
||||
北京市+朝阳区+海淀区
|
||||
上海市+浦东新区+徐汇区
|
||||
```
|
||||
|
||||
### 考试场景示例
|
||||
|
||||
考试场景(`scene=3`)支持设置答案、分值和评分机制。语法在题型后追加 `[答案:...]`、`[分数:...]`、`[全部]`/`[部分]`/`[按空]`/`[人工]`。
|
||||
|
||||
```
|
||||
期中考试
|
||||
|
||||
考生姓名:____ 班级:____ 考号:____
|
||||
|
||||
1. 在下列物体中,难溶于水的物体是?[单选题][答案:D][分数:5][全部]
|
||||
味精
|
||||
酱油
|
||||
酒精
|
||||
食用油
|
||||
|
||||
2. 地球是太阳系中最大的行星[判断题][答案:A][分数:3][全部]
|
||||
错误
|
||||
正确
|
||||
|
||||
3. 哪些颜色属于奥运五环的颜色?[多选题][答案:A、B、D][分数:5][部分]
|
||||
蓝色
|
||||
黑色
|
||||
紫色
|
||||
红色
|
||||
棕色
|
||||
|
||||
4. 哪些是哺乳动物?[不定项选择题][答案:B、C][分数:4][部分]
|
||||
鳄鱼
|
||||
鲸鱼
|
||||
海豚
|
||||
蜥蜴
|
||||
|
||||
5. 古诗文默写[多项填空题][答案:1.秋风萧瑟、2.洪波涌起][分数:2、3][按空]
|
||||
树木丛生,百草丰茂。____,____。(曹操《观沧海》)
|
||||
|
||||
6. 以"一件令人感动的事"为题写一篇小作文[问答题][分数:20][人工]
|
||||
```
|
||||
|
||||
### 测评场景示例
|
||||
|
||||
测评场景(`scene=6`)使用 `[测评单选题]`、`[测评多选题]` 等专用题型:
|
||||
|
||||
```
|
||||
职业性格测评
|
||||
|
||||
本测评将帮助您了解自己的职业性格类型,请根据真实感受作答。
|
||||
|
||||
1. 在团队合作中,您通常扮演什么角色?[测评单选题]
|
||||
领导者
|
||||
协调者
|
||||
执行者
|
||||
创意提供者
|
||||
|
||||
2. 以下哪些描述符合您的工作风格?[测评多选题]
|
||||
注重细节
|
||||
喜欢创新
|
||||
善于沟通
|
||||
偏好独立工作
|
||||
|
||||
3. 请评价您对当前工作的满意程度[量表题](1分非常不满意,5分非常满意)
|
||||
1~5
|
||||
|
||||
4. 请对以下陈述表示您的同意程度[矩阵单选题]
|
||||
非常同意 同意 中立 不同意 非常不同意
|
||||
我善于处理压力
|
||||
我喜欢接受新挑战
|
||||
我注重工作与生活的平衡
|
||||
|
||||
5. 请为以下能力自评打分[矩阵量表题](1分最低,5分最高)
|
||||
1~5
|
||||
沟通能力
|
||||
领导能力
|
||||
学习能力
|
||||
团队协作
|
||||
|
||||
6. 请简要描述您的职业目标[多行文本题]
|
||||
```
|
||||
|
||||
### 请求体示例
|
||||
|
||||
```json
|
||||
{
|
||||
"text": "员工满意度调查\n\n为了给您提供更好的服务,希望您能抽出几分钟时间。\n\n1. 您在公司工作了多久?[单选题]\n1年以下\n1-3年\n3-5年\n5年以上\n\n2. 您对以下哪些方面比较满意?[多选题]\n工作环境\n薪资福利\n团队氛围\n职业发展\n\n3. 请对整体工作满意度打分[量表题](5分表示非常满意,1分表示非常不满意)\n1~5\n\n4. 您有什么建议或意见?[多行文本题]"
|
||||
}
|
||||
```
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 创建带内容的问卷
|
||||
|
||||
```
|
||||
create_survey(text="满意度调查\n\n1. 您对工作环境是否满意?[单选题]\n非常满意\n满意\n一般\n不满意\n\n2. 您有什么建议?[多行文本题]")
|
||||
```
|
||||
|
||||
### 创建在线考试
|
||||
|
||||
```
|
||||
create_survey(scene=3, text="期中考试\n\n1. 1+1=?[单选题][答案:B][分数:5][全部]\n1\n2\n3\n4\n\n2. 以下哪些是偶数?[多选题][答案:A、C][分数:4][部分]\n2\n3\n4\n5\n\n3. 请默写古诗[问答题][分数:10][人工]")
|
||||
```
|
||||
|
||||
### 创建投票
|
||||
|
||||
```
|
||||
create_survey(scene=8, text="年度最佳员工投票\n\n1. 请选择您心目中的最佳员工[单选题]\n张三\n李四\n王五")
|
||||
```
|
||||
|
||||
### 创建测评
|
||||
|
||||
```
|
||||
create_survey(scene=6, text="性格测评\n\n1. 遇到困难时,您通常会?[测评单选题]\n独立解决\n寻求帮助\n暂时搁置\n\n2. 请为自信程度打分[量表题]\n1~5")
|
||||
```
|
||||
|
||||
### 指定项目下创建
|
||||
|
||||
```
|
||||
create_survey(project_id=100, text="项目反馈问卷\n\n1. 项目进展如何?[单选题]\n按计划进行\n有延迟\n已完成")
|
||||
```
|
||||
|
||||
### mcporter 调用
|
||||
|
||||
```bash
|
||||
# 创建调查
|
||||
mcporter call tencent-survey.create_survey --args '{"text": "满意度调查\n\n1. 满意吗?[单选题]\n满意\n不满意"}'
|
||||
|
||||
# 指定场景(投票)
|
||||
mcporter call tencent-survey.create_survey --args '{"scene": 8, "text": "投票\n\n1. 选谁?[单选题]\n甲\n乙"}'
|
||||
```
|
||||
|
||||
## 错误码
|
||||
|
||||
| error.type | 错误描述 | 解决方案 |
|
||||
|------------|---------|---------|
|
||||
| `permission_denied` | 无创建权限 | 确认 Token 有创建问卷的权限 |
|
||||
| `invalid_text_format` | 文本内容格式错误 | 检查 text 语法是否正确,参考上方语法说明 |
|
||||
| `invalid_argument` | 参数校验不通过 | 检查参数类型和值是否正确 |
|
||||
| `resource_exhausted` | 创建数量超过限制 | 当前团队问卷数量已达上限 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **text 为必填参数**:必须提供 text 内容
|
||||
2. **project_id 需要有效**:传入无效的 project_id 会导致创建失败
|
||||
3. **非幂等操作**:每次调用都会创建一份新问卷,请勿重复调用
|
||||
4. **换行必须用 `\n`**:JSON 中不能直接换行,必须用 `\n` 替代
|
||||
5. **选项不需要字母前缀**:选项直接写内容即可(如 `满意`),不需要写 `A. 满意`
|
||||
6. **题目内不允许空行**:一道题目的标题和选项之间不能有空行,否则会被识别为两道题
|
||||
7. **量表范围用 `~`**:量表题使用 `1~5` 格式(全角 `~` 和半角 `~` 均支持)
|
||||
8. **考试场景题型差异**:考试场景额外支持 `[判断题]`、`[不定项选择题]`、`[问答题]`,答案索引从 A 开始
|
||||
9. **测评场景题型差异**:测评场景的选择题需使用 `[测评单选题]`、`[测评多选题]`
|
||||
|
||||
## Annotations(工具注解)
|
||||
|
||||
| 注解 | 值 | 说明 |
|
||||
|------|---|------|
|
||||
| `readOnlyHint` | false | 非只读操作 |
|
||||
| `destructiveHint` | false | 非破坏性操作 |
|
||||
| `idempotentHint` | false | **非幂等**,每次调用都创建新问卷 |
|
||||
| `openWorldHint` | false | 内部调用 |
|
||||
@@ -0,0 +1,231 @@
|
||||
# get_survey 工具参考
|
||||
|
||||
## 概述
|
||||
|
||||
获取指定问卷的详细信息,包括标题、设置、页面、题目和选项。同时返回纯文本格式的问卷内容(`text` 字段,DSL 格式)。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `survey_id` | number | **是** | 问卷 ID |
|
||||
|
||||
## 返回值
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 292192,
|
||||
"hash": "abc123def",
|
||||
"scene": "1",
|
||||
"title": "员工满意度调查",
|
||||
"prefix": "欢迎参与本次调查",
|
||||
"suffix": "感谢您的参与",
|
||||
"state": 2,
|
||||
"page_count": 1,
|
||||
"topic_count": 3,
|
||||
"started_at": "2026-01-15 00:00:00",
|
||||
"end_at": "2026-07-01 00:00:00",
|
||||
"createTime": 1736899800,
|
||||
"updateTime": 1736899900,
|
||||
"creator_user_id": 60000000001,
|
||||
"project": {
|
||||
"id": 1234,
|
||||
"name": "2026年度调查"
|
||||
},
|
||||
"text": "员工满意度调查\n\n欢迎参与本次调查\n\n1. 您对工作环境是否满意?[单选题]\n非常满意\n满意\n一般\n不满意\n\n2. 请对整体满意度打分[量表题]\n1~5\n\n3. 请填写您的建议[多行文本题]",
|
||||
"survey_dsl": {
|
||||
"code": "if `q-1-xxxx::o1` then show `q2`",
|
||||
"errors": []
|
||||
},
|
||||
"pages": [
|
||||
{
|
||||
"id": "p1",
|
||||
"index": "0",
|
||||
"questions": [
|
||||
{
|
||||
"id": "q-1-xxxx",
|
||||
"index": 1,
|
||||
"type": "radio",
|
||||
"sub_type": 0,
|
||||
"title": "您对工作环境是否满意?",
|
||||
"description": "",
|
||||
"required": true,
|
||||
"options": [
|
||||
{"id": "o1", "text": "非常满意"},
|
||||
{"id": "o2", "text": "满意"},
|
||||
{"id": "o3", "text": "一般"},
|
||||
{"id": "o4", "text": "不满意"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "q2",
|
||||
"index": 2,
|
||||
"type": "star",
|
||||
"title": "请对整体满意度打分",
|
||||
"required": true,
|
||||
"starBeginNum": 1,
|
||||
"starNum": 5
|
||||
},
|
||||
{
|
||||
"id": "q3",
|
||||
"index": 3,
|
||||
"type": "textarea",
|
||||
"title": "请填写您的建议",
|
||||
"required": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Survey 对象
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | number | 问卷 ID |
|
||||
| `hash` | string | 问卷 hash,用于拼接投放链接 `https://wj.qq.com/s2/{id}/{hash}` |
|
||||
| `scene` | string | 问卷场景:`"1"`=调查, `"3"`=考试, `"6"`=测评, `"8"`=投票 |
|
||||
| `title` | string | 问卷标题(**可能包含 HTML 标签**) |
|
||||
| `prefix` | string | 欢迎语 |
|
||||
| `suffix` | string | 结束语 |
|
||||
| `state` | number | 状态:0=草稿, 2=回收中, 3=暂停回收 |
|
||||
| `page_count` | number | 页数 |
|
||||
| `topic_count` | number | 问题数 |
|
||||
| `started_at` | string | 回收开始时间 |
|
||||
| `end_at` | string | 回收结束时间 |
|
||||
| `createTime` | number | 创建时间(时间戳) |
|
||||
| `updateTime` | number | 更新时间(时间戳) |
|
||||
| `creator_user_id` | number | 问卷创建者的用户 ID |
|
||||
| `project` | object | 问卷所属项目 |
|
||||
| `project.id` | number | 项目 ID |
|
||||
| `project.name` | string | 项目名称 |
|
||||
| `text` | string | 纯文本格式的问卷内容(DSL 格式),包含标题、引导语和所有题目。可直接用于 `update_question` 等工具的参考 |
|
||||
| `survey_dsl` | object | 问卷自定义逻辑信息 |
|
||||
| `survey_dsl.code` | string | 当前自定义逻辑代码(DSL 脚本),可作为 `update_logic` 的参考。为空字符串时表示未设置逻辑 |
|
||||
| `survey_dsl.errors` | array | 逻辑代码中的语法错误列表,为空数组时表示无错误 |
|
||||
| `pages` | array | 页面列表 |
|
||||
|
||||
### Page 对象
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 单页标识 |
|
||||
| `index` | string | 序号 |
|
||||
| `questions` | array | 该页面下的题目列表 |
|
||||
|
||||
### Question 对象
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 题目 ID |
|
||||
| `index` | number | 题目编号 |
|
||||
| `type` | string | 题目类型(见下方 type 枚举) |
|
||||
| `sub_type` | number | 题目子类型(有 sub_type 时优先根据 sub_type 判断) |
|
||||
| `title` | string | 题目标题 |
|
||||
| `description` | string | 题目备注 |
|
||||
| `required` | boolean | 是否必答 |
|
||||
| `options` | array | 选项列表(仅选择题有) |
|
||||
| `hidden` | boolean | 是否隐藏 |
|
||||
| `random` | boolean | 选项是否随机 |
|
||||
| `goto` | object | 答题后跳转题目 |
|
||||
| `maxlength` | object | 多选题最多可选 / 文本题最大字数 |
|
||||
| `starBeginNum` | number | 量表题起始值 |
|
||||
| `starNum` | number | 量表范围(2~10) |
|
||||
| `starShowCustomStart` | string | 量表题起始文案 |
|
||||
| `starShowCustomEnd` | string | 量表题末尾文案 |
|
||||
| `subTitles` | array | 矩阵题子问题列表 |
|
||||
| `subTitles[].id` | string | 矩阵题子问题 ID |
|
||||
| `subTitles[].text` | string | 矩阵题子问题文本 |
|
||||
| `levels` | array | 联动题各级标题 |
|
||||
| `groups` | array | 联动题选项列表(嵌套) |
|
||||
|
||||
### Option 对象
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 选项 ID |
|
||||
| `text` | string | 选项文案 |
|
||||
| `exclusive` | boolean | 多选题中是否为互斥选项 |
|
||||
| `noRandom` | boolean | 选项随机时是否固定当前选项位置 |
|
||||
| `goto` | object | 选择后跳转题目 |
|
||||
| `display` | object | 选择后显示题目 |
|
||||
|
||||
### type 枚举
|
||||
|
||||
| type | 说明 |
|
||||
|------|------|
|
||||
| `radio` | 单选 |
|
||||
| `checkbox` | 多选 |
|
||||
| `select` | 下拉 |
|
||||
| `text` | 单行文本 |
|
||||
| `textarea` | 多行文本 |
|
||||
| `blanks` | 填空 |
|
||||
| `star` | 量表/NPS |
|
||||
| `sort` | 排序 |
|
||||
| `matrix_radio` | 矩阵单选 |
|
||||
| `matrix_checkbox` | 矩阵多选 |
|
||||
| `matrix_star` | 矩阵量表 |
|
||||
| `matrix_blank` | 矩阵填空 |
|
||||
| `chained_selects` | 联动 |
|
||||
| `upload` | 图片/文件 |
|
||||
| `description` | 文本描述 |
|
||||
| `datetime` | 日期/时间 |
|
||||
| `signature` | 手写签名 |
|
||||
| `address` | 地理位置 |
|
||||
| `phone` | 手机号 |
|
||||
| `sheet` | 自增表格 |
|
||||
|
||||
### 基本设置字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `prev` | boolean | 是否允许回到上一页 |
|
||||
| `titleIndex` | boolean | 是否显示题目序号 |
|
||||
| `login_check` | boolean | 是否开启登录验证 |
|
||||
| `answer_count` | number | 允许回答次数 |
|
||||
| `whitelist_enable` | boolean | 是否开启白名单 |
|
||||
| `redirect_url` | string | 答题后跳转链接 |
|
||||
| `webhook_url` | string | 答题后推送数据的地址 |
|
||||
| `is_allow_update_answer` | boolean | 是否允许修改答案 |
|
||||
| `is_enabled_location` | boolean | 是否获取用户位置信息 |
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 直接调用
|
||||
|
||||
```
|
||||
get_survey(survey_id=292192)
|
||||
```
|
||||
|
||||
### mcporter 调用
|
||||
|
||||
```bash
|
||||
mcporter call tencent-survey.get_survey --args '{"survey_id": 292192}'
|
||||
```
|
||||
|
||||
## 错误码
|
||||
|
||||
| error.type | 错误描述 | 解决方案 |
|
||||
|------------|---------|---------|
|
||||
| `invalid_auth_status` | 权限类型错误 | 检查 Token 权限 |
|
||||
| `claim_error` | 权限校验错误 | 问卷不属于当前 Token 绑定的团队 |
|
||||
| `get_survey_error` | 获取数据错误 | 确认 survey_id 正确且问卷存在 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **title 可能包含 HTML 标签**:如 `<p>标题</p>`、`<br>` 等,展示给用户前建议清理
|
||||
2. **嵌套结构**:数据为 `pages[] → questions[] → options[]`,需要递归解析
|
||||
3. **type 与 sub_type**:有 `sub_type` 时优先根据 `sub_type` 判断题目类型
|
||||
4. **投放链接拼接**:`https://wj.qq.com/s2/{id}/{hash}`
|
||||
5. **text 字段**:返回纯文本 DSL 格式的问卷内容,包含标题和所有题目。该字段可作为 `update_question` 工具的参考,了解当前问卷的文本结构
|
||||
6. **survey_dsl 字段**:返回当前问卷的自定义逻辑代码(`code`)和错误信息(`errors`)。`code` 可作为 `update_logic` 工具的参考,了解当前已设置的逻辑规则。追加逻辑时需在原有 `code` 基础上修改
|
||||
|
||||
## Annotations(工具注解)
|
||||
|
||||
| 注解 | 值 | 说明 |
|
||||
|------|---|------|
|
||||
| `readOnlyHint` | true | 只读操作,不修改任何数据 |
|
||||
| `destructiveHint` | false | 非破坏性操作 |
|
||||
| `idempotentHint` | true | 幂等操作,多次调用结果一致 |
|
||||
| `openWorldHint` | false | 内部调用 |
|
||||
@@ -0,0 +1,254 @@
|
||||
# list_answers 工具参考
|
||||
|
||||
## 概述
|
||||
|
||||
获取指定问卷的回答列表,支持游标分页。返回回答总数、回答详情列表和最后一条回答 ID(用于翻页)。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `survey_id` | number | **是** | 问卷 ID |
|
||||
| `per_page` | number | 否 | 每页返回条数,默认 20,最大 1000 |
|
||||
| `last_answer_id` | number | 否 | 上一页最后一条回答的 ID,用于翻页。默认 0 表示从头开始 |
|
||||
|
||||
## 返回值
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 150,
|
||||
"list": [
|
||||
{
|
||||
"id": 1,
|
||||
"survey_id": 292192,
|
||||
"answer_id": 1,
|
||||
"respondent_nickname": "用户A",
|
||||
"started_at": "2026-01-15 10:30:00",
|
||||
"ended_at": "2026-01-15 10:35:22",
|
||||
"score": 0,
|
||||
"country": "中国",
|
||||
"province": "广东",
|
||||
"city": "深圳",
|
||||
"answer": [
|
||||
{
|
||||
"id": "p1",
|
||||
"questions": [
|
||||
{
|
||||
"id": "q-1-xxxx",
|
||||
"identity": "q-1-abcd1234",
|
||||
"title": "您对工作环境是否满意?",
|
||||
"type": "radio",
|
||||
"sub_type": 0,
|
||||
"text": "非常满意",
|
||||
"options": [
|
||||
{"id": "o1", "text": "非常满意", "selected": true},
|
||||
{"id": "o2", "text": "满意"},
|
||||
{"id": "o3", "text": "一般"},
|
||||
{"id": "o4", "text": "不满意"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "q2",
|
||||
"identity": "q-1-efgh5678",
|
||||
"title": "请填写您的建议",
|
||||
"type": "textarea",
|
||||
"sub_type": 0,
|
||||
"text": "希望改善食堂伙食"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"last_answer_id": 20
|
||||
}
|
||||
```
|
||||
|
||||
### 顶层字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `total` | number | 问卷回答**总数**(全量统计,非当前页条数) |
|
||||
| `list` | array | 当前页的回答列表(`AnswerPayload[]`) |
|
||||
| `last_answer_id` | number | 当前页最后一条回答的 ID,用于请求下一页 |
|
||||
|
||||
### AnswerPayload 对象
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | number | 回答记录 ID |
|
||||
| `survey_id` | number | 问卷 ID |
|
||||
| `answer_id` | number | 回答序号 |
|
||||
| `respondent_id` | number | 答题者 ID |
|
||||
| `respondent_nickname` | string | 答题者昵称 |
|
||||
| `respondent_avatar` | string | 答题者头像 |
|
||||
| `respondent_type` | string | 答题者类型 |
|
||||
| `openid` | string | 答题者 OpenID |
|
||||
| `started_at` | string | 开始答题时间 |
|
||||
| `ended_at` | string | 提交答题时间 |
|
||||
| `score` | number | 考试/测评场景下的得分 |
|
||||
| `country` | string | 答题者所在国家 |
|
||||
| `province` | string | 答题者所在省份 |
|
||||
| `city` | string | 答题者所在城市 |
|
||||
| `ua` | string | 答题者 User-Agent |
|
||||
| `ip` | string | 答题者 IP 地址 |
|
||||
| `answer` | array | 回答内容(按页面分组),见下方结构说明 |
|
||||
|
||||
### answer 嵌套结构
|
||||
|
||||
回答内容按 `页面 → 题目` 的层级组织:
|
||||
|
||||
```
|
||||
answer[] ← 页面列表
|
||||
├── id: "p1" ← 页面 ID
|
||||
└── questions[] ← 该页面下的题目回答
|
||||
├── id: "q1" ← 题目 ID
|
||||
├── identity: "q-1-xxxx" ← 题目唯一标识
|
||||
├── title: "题目标题" ← 题目标题
|
||||
├── type: "radio" ← 题型
|
||||
├── text: "用户填写的内容" ← 文本类题目的回答文本
|
||||
├── options[] ← 选择题的选项(含选中状态)
|
||||
├── groups[] ← 矩阵题的分组回答
|
||||
└── blanks[] ← 填空题的填空内容
|
||||
```
|
||||
|
||||
### Question(回答中的题目)字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | string | 题目 ID |
|
||||
| `identity` | string | 题目唯一标识(如 `q-1-xxxx`) |
|
||||
| `title` | string | 题目标题 |
|
||||
| `description` | string | 题目描述 |
|
||||
| `type` | string | 题型(`radio`/`checkbox`/`text`/`textarea`/`star` 等) |
|
||||
| `sub_type` | number | 题目子类型 |
|
||||
| `text` | any | 回答内容(文本题为填写的文字,选择题为选中选项的文本) |
|
||||
| `options` | array | 选择题的选项列表(含选中状态) |
|
||||
| `groups` | array | 矩阵题的分组回答 |
|
||||
| `blanks` | array | 填空题的各填空内容 |
|
||||
| `id_list` | array | 选中选项的 ID 列表 |
|
||||
| `text_list` | array | 选中选项的文本列表 |
|
||||
| `signature_id` | string | 手写签名 ID |
|
||||
| `files` | array | 附件题的文件列表 |
|
||||
|
||||
## 分页机制(重要)
|
||||
|
||||
`list_answers` 使用**游标分页**(Cursor-based Pagination),不是传统的 offset 分页。
|
||||
|
||||
### 工作原理
|
||||
|
||||
- 每次请求返回一页数据和 `last_answer_id`
|
||||
- 将上一次返回的 `last_answer_id` 作为下一次请求的参数,获取下一页
|
||||
- 数据按 answer_id **升序**排列
|
||||
|
||||
### 翻页流程
|
||||
|
||||
```
|
||||
# 第一页:不传 last_answer_id(默认从头开始)
|
||||
list_answers(survey_id=292192)
|
||||
→ { total: 150, list: [...20条...], last_answer_id: 20 }
|
||||
|
||||
# 第二页:传入上一页返回的 last_answer_id
|
||||
list_answers(survey_id=292192, last_answer_id=20)
|
||||
→ { total: 150, list: [...20条...], last_answer_id: 45 }
|
||||
|
||||
# 第三页
|
||||
list_answers(survey_id=292192, last_answer_id=45)
|
||||
→ { total: 150, list: [...20条...], last_answer_id: 78 }
|
||||
|
||||
# ...继续翻页...
|
||||
|
||||
# 最后一页:返回不足 per_page 条,翻页结束
|
||||
list_answers(survey_id=292192, last_answer_id=135)
|
||||
→ { total: 150, list: [...10条...], last_answer_id: 150 }
|
||||
```
|
||||
|
||||
### 判断是否还有下一页
|
||||
|
||||
| 条件 | 含义 |
|
||||
|------|------|
|
||||
| `list.length == per_page` | 可能还有下一页,继续用 `last_answer_id` 翻页 |
|
||||
| `list.length < per_page` | **已到最后一页**,无需继续翻页 |
|
||||
| `list` 为空数组 | **没有更多数据** |
|
||||
|
||||
> ⚠️ **注意**:`total` 是问卷回答总数,不是当前页的条数。判断翻页是否结束,应该看 `list.length` 是否小于 `per_page`,而不是看 `total`。
|
||||
|
||||
### 获取全部回答的伪代码
|
||||
|
||||
```python
|
||||
all_answers = []
|
||||
last_id = 0
|
||||
|
||||
while True:
|
||||
result = list_answers(survey_id=292192, per_page=100, last_answer_id=last_id)
|
||||
all_answers.extend(result.list)
|
||||
|
||||
if len(result.list) < 100: # 不足一页,已到末尾
|
||||
break
|
||||
|
||||
last_id = result.last_answer_id
|
||||
|
||||
print(f"共 {result.total} 条回答,已全部获取 {len(all_answers)} 条")
|
||||
```
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 获取首页回答
|
||||
|
||||
```
|
||||
list_answers(survey_id=292192)
|
||||
```
|
||||
|
||||
### 指定每页条数
|
||||
|
||||
```
|
||||
list_answers(survey_id=292192, per_page=50)
|
||||
```
|
||||
|
||||
### 翻页获取下一页
|
||||
|
||||
```
|
||||
list_answers(survey_id=292192, per_page=50, last_answer_id=45)
|
||||
```
|
||||
|
||||
### mcporter 调用
|
||||
|
||||
```bash
|
||||
# 获取首页(默认 20 条)
|
||||
mcporter call tencent-survey.list_answers --args '{"survey_id": 292192}'
|
||||
|
||||
# 每页 100 条
|
||||
mcporter call tencent-survey.list_answers --args '{"survey_id": 292192, "per_page": 100}'
|
||||
|
||||
# 翻页
|
||||
mcporter call tencent-survey.list_answers --args '{"survey_id": 292192, "per_page": 100, "last_answer_id": 45}'
|
||||
```
|
||||
|
||||
## 错误码
|
||||
|
||||
| error.type | 错误描述 | 解决方案 |
|
||||
|------------|---------|---------|
|
||||
| `invalid_auth_status` | 权限类型错误 | 检查 Token 权限 |
|
||||
| `claim_error` | 权限校验错误 | 问卷不属于当前 Token 绑定的团队 |
|
||||
| `invalid_argument` | 参数校验不通过 | 检查 survey_id 是否正确 |
|
||||
| `get_answers_error` | 获取回答列表错误 | 确认 survey_id 正确且问卷存在 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **游标分页**:使用 `last_answer_id` 翻页,不支持跳页;必须按顺序逐页获取
|
||||
2. **需要循环翻页**:如果回答数量多于 `per_page`,必须循环调用直到获取完所有数据
|
||||
3. **per_page 上限 1000**:即使传入大于 1000 的值,也会被截断为 1000
|
||||
4. **total 是全量统计**:`total` 字段表示问卷回收的总回答数,不随分页变化
|
||||
5. **answer 嵌套结构**:每条回答的 `answer` 字段按 `页面 → 题目` 嵌套,需要递归解析
|
||||
6. **题目回答内容**:不同题型的回答内容存储方式不同(`text` / `options` / `blanks` / `groups`),需根据 `type` 字段判断
|
||||
|
||||
## Annotations(工具注解)
|
||||
|
||||
| 注解 | 值 | 说明 |
|
||||
|------|---|------|
|
||||
| `readOnlyHint` | true | 只读操作,不修改任何数据 |
|
||||
| `destructiveHint` | false | 非破坏性操作 |
|
||||
| `idempotentHint` | true | 幂等操作,多次调用结果一致 |
|
||||
| `openWorldHint` | false | 内部调用 |
|
||||
@@ -0,0 +1,235 @@
|
||||
# update_logic 工具参考
|
||||
|
||||
## 概述
|
||||
|
||||
更新问卷的自定义逻辑设置(DSL 代码)。可通过 `get_survey` 返回的 `survey_dsl` 字段获取当前逻辑代码,修改后整体传入。传入空字符串可清空所有逻辑。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `survey_id` | number | **是** | 问卷 ID |
|
||||
| `dsl` | string | **是** | 问卷自定义逻辑代码(DSL 脚本语法) |
|
||||
|
||||
> **重要**:`dsl` 中引用题目和选项时需使用反引号包裹的 ID 格式(如 `` `q-1-abcd::o-100-ABCD` ``),ID 可从 `get_survey` 返回的题目和选项信息中获取。
|
||||
|
||||
## 返回值
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"survey_id": 716128,
|
||||
"result": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"survey_id": 716128,
|
||||
"result": "failed",
|
||||
"error": "invalid_dsl_syntax: ..."
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `survey_id` | number | 问卷 ID |
|
||||
| `result` | string | `"success"` 表示成功,`"failed"` 表示失败 |
|
||||
| `error` | string | 仅失败时返回,错误描述信息 |
|
||||
|
||||
## DSL 语法说明
|
||||
|
||||
自定义逻辑使用 DSL(Domain-Specific Language)脚本语法。多条规则用换行分隔。
|
||||
|
||||
> 📖 完整语法文档:https://wj.qq.com/docs/dsl/grammar
|
||||
|
||||
### 引用格式
|
||||
|
||||
在通过 MCP 工具编写 DSL 时,题目和选项 ID 需要使用**反引号包裹**:
|
||||
|
||||
| 元素 | 格式 | 示例 |
|
||||
|------|------|------|
|
||||
| 题目 | `` `q-1-xxxx` `` | `` `q-1-abcd` `` |
|
||||
| 选项 | `` `q-1-xxxx::o-100-ABCD` `` | `` `q-1-abcd::o-100-EFGH` `` |
|
||||
| 矩阵子问题 | `` `q-1-xxxx::s-200-IJKL` `` | `` `q-1-abcd::s-200-IJKL` `` |
|
||||
| 填空项 | `` `q-1-xxxx::b-300-MNOP` `` | `` `q-1-abcd::b-300-MNOP` `` |
|
||||
|
||||
> **注意**:ID 值必须从 `get_survey` 返回的数据中获取,不能自行构造。
|
||||
|
||||
### 条件操作符
|
||||
|
||||
| 操作符 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `and` | 逻辑与 | `if Q1A1 and Q2A2 then show Q3` |
|
||||
| `or` | 逻辑或 | `if Q1A1 or Q1A2 then show Q2` |
|
||||
| `not` | 逻辑非 | `if Q1 and not Q1A3 then show Q2` |
|
||||
| `()` | 分组 | `if (Q1A1 or Q1A2) and Q2A1 then show Q3` |
|
||||
| `>` `>=` `==` `<` `<=` | 比较 | `if Q1 > 5 then show Q2` |
|
||||
| `+` `-` `*` `/` | 算数运算 | `if Q1 + Q2 > 10 then show Q3` |
|
||||
|
||||
### 行为控制语句
|
||||
|
||||
| 语句类型 | 语法格式 | 说明 |
|
||||
|---------|---------|------|
|
||||
| **条件显示** | `if <条件> then show <目标>` | 满足条件时显示题目或选项 |
|
||||
| **条件隐藏** | `if <条件> then hide <目标>` | 满足条件时隐藏题目或选项 |
|
||||
| **直接隐藏** | `hide <目标>` | 直接隐藏题目或选项(无条件) |
|
||||
| **连线跳转** | `if <条件> then branch from <题目> to <目标>` | 满足条件时从某题跳转到指定题目或 `END` |
|
||||
| **内容替换** | `replace "<文本>" in <题目> title with <来源>` | 将题目标题中的指定文本替换为另一题的回答 |
|
||||
| **随机排序** | `shuffle <范围>` | 对题目或选项进行随机排序 |
|
||||
| **随机抽取** | `random show <数量> from <范围>` | 从题目或选项中随机抽取指定数量显示 |
|
||||
| **自动圈选** | `set <选项>` | 自动选中指定选项 |
|
||||
| **自动填充** | `set <题目> = <来源>` | 自动填充文本题内容 |
|
||||
|
||||
### 特殊关键字
|
||||
|
||||
| 关键字 | 说明 | 示例 |
|
||||
|--------|------|------|
|
||||
| `END` | 结束页,用于跳转到问卷结尾 | `branch from Q1 to END` |
|
||||
| `len` | 答案个数 | `if len Q1 > 2 then show Q3` |
|
||||
| `index` | 答案排在第几位 | `if index Q1A1 == 1 then show Q2` |
|
||||
| `RANDBETWEEN(min, max)` | 随机生成整数 | `if RANDBETWEEN(1, 10) > 5 then show Q2` |
|
||||
| `LANG()` | 获取答题者语言 | `if LANG() == "zhs" then show Q1` |
|
||||
| `PARAMS("key")` | 获取自定义参数 | `if PARAMS("source") == "wechat" then show Q2` |
|
||||
| `#` | 注释 | `# 这是注释,不会被执行` |
|
||||
|
||||
### 范围表示
|
||||
|
||||
| 格式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `Q1~3` | 题目 Q1 到 Q3 | `shuffle Q1~3` |
|
||||
| `Q1A1~4` | 题目 Q1 的选项 A1 到 A4 | `shuffle Q1A1~4` |
|
||||
| `Q1,Q3,Q5` | 不连续的题目 | `random show 1 from Q1,Q3,Q5` |
|
||||
|
||||
> 当连续 3 个以上序号时,系统会自动合并为 `~` 格式。
|
||||
|
||||
### weight 权重
|
||||
|
||||
`random show` 支持设置权重:
|
||||
|
||||
```
|
||||
random show 1 from Q1~3 weight by 1:1:2
|
||||
```
|
||||
|
||||
表示 Q3 被抽取的概率是 Q1、Q2 的两倍。
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 典型工作流
|
||||
|
||||
```
|
||||
# 1. 先获取问卷详情,确认题目和选项 ID
|
||||
get_survey(survey_id=716128)
|
||||
# 返回中查看 survey_dsl.code 获取当前逻辑代码
|
||||
# 从 pages → questions → options 中获取题目和选项的 ID
|
||||
|
||||
# 2. 编写并更新逻辑
|
||||
update_logic(survey_id=716128, dsl="if `q-1-abcd::o-100-EFGH` then show `q-1-ijkl`")
|
||||
```
|
||||
|
||||
### 条件显示逻辑
|
||||
|
||||
```
|
||||
# 选择了第1题的第1个选项时,显示第2题
|
||||
update_logic(survey_id=716128, dsl="if `q-1-abcd::o-100-EFGH` then show `q-1-ijkl`")
|
||||
```
|
||||
|
||||
### 多条件组合
|
||||
|
||||
```
|
||||
# 第1题选了A 且 第2题选了B 时,显示第3题
|
||||
update_logic(survey_id=716128, dsl="if `q-1-abcd::o-100-EFGH` and `q-1-ijkl::o-200-MNOP` then show `q-1-qrst`")
|
||||
```
|
||||
|
||||
### 连线跳转(甄别题)
|
||||
|
||||
```
|
||||
# 选择了第1题某选项时,跳转到问卷结束
|
||||
update_logic(survey_id=716128, dsl="if `q-1-abcd::o-100-EFGH` then branch from `q-1-abcd` to END")
|
||||
```
|
||||
|
||||
### 内容替换
|
||||
|
||||
```
|
||||
# 将第3题标题中的"XXX"替换为第1题的回答
|
||||
update_logic(survey_id=716128, dsl="replace \"XXX\" in `q-1-qrst` title with `q-1-abcd`")
|
||||
```
|
||||
|
||||
### 随机排序与随机抽取
|
||||
|
||||
```
|
||||
# 随机排序第1题的选项1~4
|
||||
update_logic(survey_id=716128, dsl="shuffle `q-1-abcd::o-100-A`~`q-1-abcd::o-100-D`")
|
||||
|
||||
# 从题目1~3中随机抽取1道显示
|
||||
update_logic(survey_id=716128, dsl="random show 1 from `q-1-abcd`~`q-1-qrst`")
|
||||
```
|
||||
|
||||
### 多条规则组合
|
||||
|
||||
```
|
||||
# 多条规则用换行分隔
|
||||
update_logic(survey_id=716128, dsl="if `q-1-abcd::o-100-EFGH` then show `q-1-ijkl`\nif `q-1-abcd::o-100-MNOP` then show `q-1-qrst`\nreplace \"XXX\" in `q-1-uvwx` title with `q-1-abcd`")
|
||||
```
|
||||
|
||||
### 清空所有逻辑
|
||||
|
||||
```
|
||||
update_logic(survey_id=716128, dsl="")
|
||||
```
|
||||
|
||||
### mcporter 调用
|
||||
|
||||
```bash
|
||||
# 设置条件显示逻辑
|
||||
mcporter call tencent-survey.update_logic --args '{"survey_id": 716128, "dsl": "if `q-1-abcd::o-100-EFGH` then show `q-1-ijkl`"}'
|
||||
|
||||
# 清空所有逻辑
|
||||
mcporter call tencent-survey.update_logic --args '{"survey_id": 716128, "dsl": ""}'
|
||||
```
|
||||
|
||||
## 权限要求
|
||||
|
||||
调用此接口需要满足以下权限条件(逐级校验):
|
||||
|
||||
| 校验层 | 说明 |
|
||||
|--------|------|
|
||||
| `WithSurveyClaims` | 问卷归属校验:问卷必须属于当前 Token 绑定的团队 |
|
||||
| `WithSurveyEditorClaims` | 编辑权限校验:当前用户需具有该问卷的编辑权限 |
|
||||
| `WithSurveyEditableClaims` | 可编辑状态校验:问卷必须处于可编辑状态(如草稿状态) |
|
||||
|
||||
## 错误码
|
||||
|
||||
| error.type | 错误描述 | 解决方案 |
|
||||
|------------|---------|---------|
|
||||
| `invalid_dsl_syntax` | DSL 语法错误 | 检查 DSL 代码语法是否正确,参考语法文档 |
|
||||
| `invalid_reference` | 引用的题目/选项 ID 不存在 | 确认 ID 从 get_survey 返回数据中获取 |
|
||||
| `claim_error` | 权限校验错误 | 问卷不属于当前 Token 绑定的团队,或无编辑权限 |
|
||||
| `survey_not_editable` | 问卷不可编辑 | 问卷可能正在回收中,需先暂停回收 |
|
||||
| `invalid_argument` | 参数校验不通过 | 检查 survey_id、dsl 是否正确 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **先获取再更新**:必须先调用 `get_survey` 获取问卷详情,从返回数据中获取正确的题目 ID 和选项 ID
|
||||
2. **整体覆盖**:`dsl` 参数为问卷的完整逻辑代码,每次调用会覆盖所有已有逻辑。如需追加规则,需先获取当前 `survey_dsl.code`,在其基础上修改后整体传入
|
||||
3. **非幂等操作**:每次调用都会覆盖原有逻辑配置
|
||||
4. **ID 使用反引号**:通过 MCP 工具编写 DSL 时,题目和选项 ID 需使用反引号包裹(如 `` `q-1-abcd::o-100-EFGH` ``)
|
||||
5. **多条规则换行分隔**:在 JSON 参数中,多条规则之间使用 `\n` 分隔
|
||||
6. **清空逻辑**:传入空字符串(`""`)可清空所有自定义逻辑
|
||||
7. **问卷状态要求**:问卷必须处于可编辑状态,正在回收中的问卷需先暂停才能编辑
|
||||
8. **与基础逻辑共存**:自定义逻辑与基础逻辑可同时设置,执行顺序为先执行基础逻辑、再执行自定义逻辑
|
||||
9. **付费功能**:自定义逻辑为付费高级功能。如调用时返回 `paid_function_trial_no_permission` 错误,说明当前团队未购买该功能权限,需引导用户前往[腾讯问卷购买页](https://wj.qq.com/pricing)进行付费升级(升级为付费版本)后再使用
|
||||
|
||||
## Annotations(工具注解)
|
||||
|
||||
| 注解 | 值 | 说明 |
|
||||
|------|---|------|
|
||||
| `readOnlyHint` | false | 非只读操作,会修改问卷逻辑配置 |
|
||||
| `destructiveHint` | false | 非破坏性操作(更新而非删除) |
|
||||
| `idempotentHint` | false | **非幂等**,每次调用都覆盖逻辑配置 |
|
||||
| `openWorldHint` | false | 内部调用 |
|
||||
@@ -0,0 +1,154 @@
|
||||
# update_question 工具参考
|
||||
|
||||
## 概述
|
||||
|
||||
更新问卷中的某一道题目。需要先用 `get_survey` 获取问卷详情以确认 `question_id`,再调用此接口更新指定题目的内容。只传入目标题目的纯文本(DSL 格式),系统会自动解析并覆盖该题。
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|:----:|------|
|
||||
| `survey_id` | number | **是** | 问卷 ID |
|
||||
| `question_id` | string | **是** | 要更新的题目 ID,格式形如 `q-1-xxxx`(从 `get_survey` 返回的题目列表中获取) |
|
||||
| `text` | string | **是** | 该题目的新文本内容(DSL 格式),只需包含这一道题 |
|
||||
|
||||
> **重要**:`question_id` 必须从 `get_survey` 返回的题目列表中获取,不能自行构造。
|
||||
|
||||
## 返回值
|
||||
|
||||
### 成功响应
|
||||
|
||||
```json
|
||||
{
|
||||
"survey_id": 716128,
|
||||
"question_id": "q-1-abcd1234",
|
||||
"result": "success"
|
||||
}
|
||||
```
|
||||
|
||||
### 失败响应
|
||||
|
||||
```json
|
||||
{
|
||||
"survey_id": 716128,
|
||||
"question_id": "q-1-abcd1234",
|
||||
"result": "failed",
|
||||
"error": "invalid_text_format: ..."
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `survey_id` | number | 问卷 ID |
|
||||
| `question_id` | string | 更新的题目 ID |
|
||||
| `result` | string | `"success"` 表示成功,`"failed"` 表示失败 |
|
||||
| `error` | string | 仅失败时返回,错误描述信息 |
|
||||
|
||||
## text 语法说明
|
||||
|
||||
`text` 参数使用与 `create_survey` 相同的 DSL 语法,但**只需写一道题**(不需要写问卷标题)。
|
||||
|
||||
### 语法格式
|
||||
|
||||
```
|
||||
题目标题[题型](描述)
|
||||
选项A
|
||||
选项B
|
||||
```
|
||||
|
||||
> **关键规则**:`[题型]` 标签**必须紧跟在标题末尾**,后面不能再有文字。`(描述)` 部分为可选。
|
||||
|
||||
### 各题型示例
|
||||
|
||||
| 题型 | text 示例 |
|
||||
|------|----------|
|
||||
| 单选题 | `"您的性别[单选题]\n男\n女"` |
|
||||
| 多选题 | `"您感兴趣的领域[多选题]\n技术\n设计\n产品\n运营"` |
|
||||
| 下拉题 | `"请选择部门[下拉题]\n研发部\n市场部\n财务部"` |
|
||||
| 排序题 | `"请排列优先级[排序题]\n功能\n性能\n安全\n体验"` |
|
||||
| 单行文本题 | `"您的姓名[单行文本题]"` |
|
||||
| 多行文本题 | `"您的建议[多行文本题]"` |
|
||||
| 量表题 | `"请打分[量表题](5分非常满意)\n1~5"` |
|
||||
| 多项填空题 | `"联系方式[多项填空题]\n手机号:____\n邮箱:____"` |
|
||||
| 矩阵单选题 | `"满意度评价[矩阵单选题]\n非常满意 满意 一般 不满意\n服务态度\n响应速度"` |
|
||||
| 段落说明 | `"以下为附加问题[段落说明]"` |
|
||||
|
||||
> 完整语法参考:`references/create_survey.md` 中的「text 文本语法详解」章节。
|
||||
|
||||
## 调用示例
|
||||
|
||||
### 典型工作流
|
||||
|
||||
```
|
||||
# 1. 先获取问卷详情,确认题目 ID
|
||||
get_survey(survey_id=716128)
|
||||
# 返回中找到目标题目,例如 id="q-1-abcd1234",类型为单选题
|
||||
|
||||
# 2. 更新该题目
|
||||
update_question(survey_id=716128, question_id="q-1-abcd1234", text="您的性别[单选题]\n男\n女\n其他")
|
||||
```
|
||||
|
||||
### 更新量表题
|
||||
|
||||
```
|
||||
update_question(survey_id=716128, question_id="q-1-efgh5678", text="请对服务打分[量表题](1分最低,10分最高)\n1~10")
|
||||
```
|
||||
|
||||
### 更新多选题
|
||||
|
||||
```
|
||||
update_question(survey_id=716128, question_id="q-1-ijkl9012", text="您常用的编程语言[多选题]\nGo\nPython\nJava\nTypeScript\nRust")
|
||||
```
|
||||
|
||||
### mcporter 调用
|
||||
|
||||
```bash
|
||||
# 更新单选题
|
||||
mcporter call tencent-survey.update_question --args '{"survey_id": 716128, "question_id": "q-1-abcd1234", "text": "您的性别[单选题]\n男\n女\n其他"}'
|
||||
|
||||
# 更新多行文本题
|
||||
mcporter call tencent-survey.update_question --args '{"survey_id": 716128, "question_id": "q-1-efgh5678", "text": "请填写您的建议[多行文本题]"}'
|
||||
```
|
||||
|
||||
## 权限要求
|
||||
|
||||
调用此接口需要满足以下权限条件(逐级校验):
|
||||
|
||||
| 校验层 | 说明 |
|
||||
|--------|------|
|
||||
| `WithSurveyClaims` | 问卷归属校验:问卷必须属于当前 Token 绑定的团队 |
|
||||
| `WithSurveyEditorClaims` | 编辑权限校验:当前用户需具有该问卷的编辑权限 |
|
||||
| `WithSurveyEditableClaims` | 可编辑状态校验:问卷必须处于可编辑状态(如草稿状态) |
|
||||
|
||||
## 错误码
|
||||
|
||||
| error.type | 错误描述 | 解决方案 |
|
||||
|------------|---------|---------|
|
||||
| `invalid_text_format` | 文本内容格式错误 | 检查 text 语法是否正确,`[题型]` 需紧跟标题末尾 |
|
||||
| `no_question_parsed` | 未能解析出任何题目 | 确认 text 包含了完整的题目内容 |
|
||||
| `unknown_question_type` | 无法识别的题型 | 检查 `[题型]` 标签是否使用了支持的题型名称 |
|
||||
| `save_question_failed` | 保存题目失败 | 服务端异常,请稍后重试 |
|
||||
| `claim_error` | 权限校验错误 | 问卷不属于当前 Token 绑定的团队,或无编辑权限 |
|
||||
| `survey_not_editable` | 问卷不可编辑 | 问卷可能正在回收中,需先暂停回收 |
|
||||
| `invalid_argument` | 参数校验不通过 | 检查 survey_id、question_id、text 是否正确 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **先获取再更新**:必须先调用 `get_survey` 获取问卷详情,从返回的题目列表中获取正确的 `question_id`
|
||||
2. **单题更新**:`text` 只需包含一道题目的内容,不要写问卷标题或多道题
|
||||
3. **非幂等操作**:每次调用都会覆盖原题目内容
|
||||
4. **题型标签位置**:`[题型]` 必须紧跟标题末尾,不能放在其他位置
|
||||
5. **问卷状态要求**:问卷必须处于可编辑状态,正在回收中的问卷需先暂停才能编辑
|
||||
6. **换行用 `\n`**:在 JSON 参数中,所有换行必须使用 `\n` 代替
|
||||
7. **选项无需字母前缀**:选项直接写内容即可(如 `满意`),不需要写 `A. 满意`
|
||||
|
||||
## Annotations(工具注解)
|
||||
|
||||
| 注解 | 值 | 说明 |
|
||||
|------|---|------|
|
||||
| `readOnlyHint` | false | 非只读操作,会修改问卷内容 |
|
||||
| `destructiveHint` | false | 非破坏性操作(更新而非删除) |
|
||||
| `idempotentHint` | false | **非幂等**,每次调用都覆盖题目内容 |
|
||||
| `openWorldHint` | false | 内部调用 |
|
||||
@@ -0,0 +1,637 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Setup script for 腾讯问卷 MCP Skill (内部 OpenClaw 版本) 一体化配置与授权脚本
|
||||
#
|
||||
# 功能:
|
||||
# 1. 检查 mcporter 是否已配置 tencent-survey(含 Authorization 可用)
|
||||
# 2. 未配置或 Token 失效时,展示授权链接
|
||||
# 3. 前台轮询等待用户完成授权,Token 获取后自动写入 mcporter 并继续
|
||||
# 4. 对超时、过期、错误等场景给出友好提示
|
||||
#
|
||||
# 用法(供 AI Agent 调用):
|
||||
# 第一步:检查状态(立即返回,不阻塞)
|
||||
# bash ./setup.sh wj_check_and_start_auth
|
||||
# 输出:
|
||||
# READY → 服务已就绪,直接执行用户任务,无需第二步
|
||||
# NONCE:<nonce> → nonce 值(在 AUTH_REQUIRED 之前输出)
|
||||
# AUTH_REQUIRED:<url> → 立即向用户展示授权链接,然后执行第二步
|
||||
# ERROR:* → 告知用户对应错误
|
||||
#
|
||||
# 第二步:等待授权完成(仅 AUTH_REQUIRED 时执行,阻塞最多约 300s)
|
||||
# bash ./setup.sh wj_wait_auth
|
||||
# 输出:
|
||||
# TOKEN_READY:ok → 授权成功,Token 已写入配置,继续执行用户任务
|
||||
# AUTH_TIMEOUT → 告知用户:授权超时,请重新发起请求
|
||||
# ERROR:* → 告知用户对应错误
|
||||
#
|
||||
# 直接执行(排查问题):
|
||||
# bash ./setup.sh
|
||||
#
|
||||
|
||||
# ── 全局配置 ──────────────────────────────────────────────────────────────────
|
||||
_WJ_API_BASE="${WJ_API_BASE_URL:-https://wj.qq.com}"
|
||||
_WJ_AUTH_PAGE="${WJ_AUTH_PAGE_URL:-https://wj.qq.com/oauth/authorize}"
|
||||
|
||||
# 从 WJ_AUTH_PAGE_URL 中提取查询参数(如 _tde_id=2952),附加到 API 请求
|
||||
_WJ_EXTRA_QUERY=""
|
||||
if [[ "$_WJ_AUTH_PAGE" == *"?"* ]]; then
|
||||
_WJ_EXTRA_QUERY="${_WJ_AUTH_PAGE#*\?}"
|
||||
fi
|
||||
|
||||
# 构造 API URL,如有额外参数则附加
|
||||
_WJ_MCP_URL="${_WJ_API_BASE}/api/v2/mcp"
|
||||
_WJ_TOKEN_POLL_URL="${_WJ_API_BASE}/api/v2/account/tokens/device-auth/poll"
|
||||
if [[ -n "$_WJ_EXTRA_QUERY" ]]; then
|
||||
_WJ_MCP_URL="${_WJ_MCP_URL}?${_WJ_EXTRA_QUERY}"
|
||||
_WJ_TOKEN_POLL_URL="${_WJ_TOKEN_POLL_URL}?${_WJ_EXTRA_QUERY}"
|
||||
fi
|
||||
_WJ_SERVICE_NAME="tencent-survey"
|
||||
|
||||
# 轮询参数:每 2s 一次,最多 150 次(约 300s)
|
||||
_WJ_POLL_INTERVAL=2
|
||||
_WJ_POLL_MAX=150
|
||||
|
||||
# 临时文件:使用基于 UID 的私有目录,避免 symlink 攻击和多用户冲突
|
||||
_WJ_TMP_DIR="${TMPDIR:-/tmp}/.wj_auth_$(id -u)"
|
||||
mkdir -p "$_WJ_TMP_DIR" 2>/dev/null
|
||||
chmod 700 "$_WJ_TMP_DIR"
|
||||
|
||||
_WJ_CODE_FILE="${_WJ_TMP_DIR}/code"
|
||||
_WJ_TOKEN_FILE="${_WJ_TMP_DIR}/token"
|
||||
_WJ_URL_FILE="${_WJ_TMP_DIR}/url"
|
||||
_WJ_NONCE_FILE="${_WJ_TMP_DIR}/nonce"
|
||||
|
||||
# ── 安全写入函数(拒绝写入符号链接)────────────────────────────────────────
|
||||
_wj_safe_write() {
|
||||
local file="$1" content="$2"
|
||||
if [[ -L "$file" ]]; then
|
||||
echo "ERROR:security - 检测到符号链接,拒绝写入: $file" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "$content" > "$file"
|
||||
chmod 600 "$file"
|
||||
}
|
||||
|
||||
# ── 清理函数 ──────────────────────────────────────────────────────────────────
|
||||
_wj_cleanup() {
|
||||
[[ -d "$_WJ_TMP_DIR" ]] && rm -rf "$_WJ_TMP_DIR"
|
||||
# 重建私有目录,确保后续写入可用
|
||||
mkdir -p "$_WJ_TMP_DIR" 2>/dev/null
|
||||
chmod 700 "$_WJ_TMP_DIR"
|
||||
}
|
||||
|
||||
# ── 检查 mcporter 是否已安装 ──────────────────────────────────────────────────
|
||||
_wj_check_mcporter() {
|
||||
if ! command -v mcporter &> /dev/null; then
|
||||
echo "⚠️ 未找到 mcporter,正在安装..."
|
||||
if command -v npm &>/dev/null; then
|
||||
npm install -g mcporter 2>&1 | tail -3
|
||||
echo "✅ mcporter 安装完成"
|
||||
else
|
||||
echo "ERROR:no_npm"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── 检查必要系统依赖 ──────────────────────────────────────────────────────────
|
||||
_wj_check_dependencies() {
|
||||
local missing=()
|
||||
command -v curl &>/dev/null || missing+=("curl")
|
||||
command -v jq &>/dev/null || missing+=("jq")
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
echo "ERROR:missing_dependencies - 缺少必要依赖: ${missing[*]}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── JSON 字段提取(使用 jq)──────────────────────────────────────────────────
|
||||
# 提取 JSON 顶层字段
|
||||
# 用法:_wj_json_get '{"code":"Ok","data":{...}}' "code" → Ok
|
||||
_wj_json_get() {
|
||||
local json="$1" key="$2"
|
||||
echo "$json" | jq -r --arg k "$key" '.[$k] // empty'
|
||||
}
|
||||
|
||||
# 提取 JSON "data" 子对象中的字段
|
||||
# 用法:_wj_json_get_data '{"code":"Ok","data":{"token":"xxx"}}' "token" → xxx
|
||||
_wj_json_get_data() {
|
||||
local json="$1" key="$2"
|
||||
echo "$json" | jq -r --arg k "$key" '.data[$k] // empty'
|
||||
}
|
||||
|
||||
# 从 mcporter config get 读取当前 Authorization Token
|
||||
# 输出:token 字符串(空则表示服务未注册或 Token 未配置)
|
||||
_wj_get_token() {
|
||||
local output
|
||||
output=$(mcporter config get "$_WJ_SERVICE_NAME" 2>/dev/null) || return 1
|
||||
|
||||
# 从输出中提取 Authorization 头的值
|
||||
local token
|
||||
token=$(echo "$output" | grep -i '^\s*Authorization:' | sed 's/.*Authorization:[[:space:]]*//' | tr -d '[:space:]')
|
||||
echo "$token"
|
||||
}
|
||||
|
||||
# ── 将 Token 写入 mcporter 配置 ───────────────────────────────────────────────
|
||||
# 用法:_wj_save_token <token>
|
||||
_wj_save_token() {
|
||||
# 添加 MCP 配置
|
||||
echo "🔧 配置 mcporter..."
|
||||
|
||||
local token="$1"
|
||||
[[ -z "$token" ]] && return 1
|
||||
|
||||
# 使用传入的 token 写入 mcporter 配置(tencent-survey)
|
||||
mcporter config add "$_WJ_SERVICE_NAME" "$_WJ_MCP_URL" \
|
||||
--header "Authorization=Bearer $token" \
|
||||
--transport http \
|
||||
--scope home
|
||||
|
||||
echo ""
|
||||
echo "✅ 配置完成!"
|
||||
echo ""
|
||||
|
||||
echo "🧪 验证配置..."
|
||||
if mcporter list 2>&1 | grep -q "$_WJ_SERVICE_NAME"; then
|
||||
echo "✅ tencent-survey 配置验证成功!"
|
||||
echo ""
|
||||
mcporter list | grep -A 1 "$_WJ_SERVICE_NAME" || true
|
||||
else
|
||||
echo "⚠️ tencent-survey 配置验证失败,请检查网络或 Token 是否有效"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "─────────────────────────────────────"
|
||||
echo "🎉 设置完成!"
|
||||
echo ""
|
||||
echo "📖 使用方法:"
|
||||
echo " mcporter call ${_WJ_SERVICE_NAME}.get_survey --args '{\"survey_id\": 12345}'"
|
||||
echo " mcporter call ${_WJ_SERVICE_NAME}.create_survey --args '{\"text\": \"问卷标题\\n\\n1. 题目\"}'"
|
||||
echo ""
|
||||
echo "🏠 腾讯问卷主页:${_WJ_API_BASE}"
|
||||
echo ""
|
||||
echo "📖 更多信息请查看 SKILL.md"
|
||||
echo ""
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── 检查 tencent-survey 服务状态 ────────────────────────────────────────────────
|
||||
# 返回值:
|
||||
# 0 = 服务正常可用(有 Token)
|
||||
# 1 = 服务未注册(mcporter config get 失败)
|
||||
# 2 = Token 为空或未配置
|
||||
_wj_check_service() {
|
||||
if ! mcporter list 2>/dev/null | grep -q "$_WJ_SERVICE_NAME"; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
local token
|
||||
token=$(_wj_get_token)
|
||||
local rc=$?
|
||||
|
||||
# mcporter config get 返回非 0 表示服务未注册
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Token 为空表示服务已注册但未配置 Authorization
|
||||
if [[ -z "$token" ]]; then
|
||||
return 2
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── 生成授权链接(调用后端 /init API 获取 code)─────────────────────────────
|
||||
# 输出:auth_url 字符串
|
||||
# code 由后端 crypto/rand 生成,保证密码学安全性
|
||||
_wj_generate_auth_url() {
|
||||
# 构造 init API URL
|
||||
local init_url="${_WJ_API_BASE}/api/v2/account/tokens/device-auth/init"
|
||||
if [[ -n "$_WJ_EXTRA_QUERY" ]]; then
|
||||
init_url="${init_url}?${_WJ_EXTRA_QUERY}"
|
||||
fi
|
||||
|
||||
# 调用后端 init 端点
|
||||
local response
|
||||
response=$(curl -s -f -X POST "$init_url" 2>/dev/null) || {
|
||||
echo "ERROR:init_request_failed - 无法连接到服务器 ${init_url}"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 解析响应中的 code
|
||||
local resp_code
|
||||
resp_code=$(_wj_json_get "$response" "code")
|
||||
local resp_upper
|
||||
resp_upper=$(echo "$resp_code" | tr '[:lower:]' '[:upper:]')
|
||||
if [[ "$resp_upper" != "OK" ]]; then
|
||||
echo "ERROR:init_failed - 服务端返回错误: $resp_code"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local code
|
||||
code=$(_wj_json_get_data "$response" "code")
|
||||
|
||||
local nonce
|
||||
nonce=$(_wj_json_get_data "$response" "nonce")
|
||||
|
||||
# 防御性校验
|
||||
if [[ ! "$code" =~ ^[0-9a-f]{16,64}$ ]]; then
|
||||
echo "ERROR:invalid_code - 服务端返回的 code 格式非法: $code"
|
||||
return 1
|
||||
fi
|
||||
if [[ -z "$nonce" ]]; then
|
||||
echo "ERROR:invalid_nonce - 服务端未返回 nonce"
|
||||
return 1
|
||||
fi
|
||||
|
||||
_wj_safe_write "$_WJ_CODE_FILE" "$code" || return 1
|
||||
_wj_safe_write "$_WJ_NONCE_FILE" "$nonce" || return 1
|
||||
# 如果 AUTH_PAGE 已包含 ?,则用 & 拼接;否则用 ?
|
||||
local sep="?"
|
||||
[[ "$_WJ_AUTH_PAGE" == *"?"* ]] && sep="&"
|
||||
echo "${_WJ_AUTH_PAGE}${sep}code=${code}&nonce=${nonce}"
|
||||
}
|
||||
|
||||
# ── 前台轮询 Token(通用函数)─────────────────────────────────────────────────
|
||||
# 用法:_wj_poll_token [log_prefix]
|
||||
# log_prefix: 日志前缀(默认 "⏳"),用于区分调用场景的输出
|
||||
#
|
||||
# 前置条件:$_WJ_CODE_FILE 已存在且包含有效的 code
|
||||
#
|
||||
# 输出(最后一行为结构化结果):
|
||||
# TOKEN_READY:<token> 授权成功
|
||||
# AUTH_TIMEOUT 超时
|
||||
# ERROR:empty_token Token 为空异常
|
||||
# ERROR:no_code 未找到授权码
|
||||
# ERROR:empty_code 授权码为空
|
||||
_wj_poll_token() {
|
||||
local prefix="${1:-⏳}"
|
||||
|
||||
# 检查 code 文件
|
||||
if [[ ! -f "$_WJ_CODE_FILE" ]]; then
|
||||
echo "ERROR:no_code - 未找到授权码,请先执行 wj_check_and_start_auth"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local code
|
||||
code=$(cat "$_WJ_CODE_FILE")
|
||||
if [[ -z "$code" ]]; then
|
||||
echo "ERROR:empty_code - 授权码为空"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local poll_sep="?"
|
||||
[[ "$_WJ_TOKEN_POLL_URL" == *"?"* ]] && poll_sep="&"
|
||||
local url="${_WJ_TOKEN_POLL_URL}${poll_sep}code=${code}"
|
||||
|
||||
local i
|
||||
for ((i=1; i<=_WJ_POLL_MAX; i++)); do
|
||||
sleep "$_WJ_POLL_INTERVAL"
|
||||
|
||||
local response
|
||||
response=$(curl -s -f -L "$url" 2>/dev/null) || {
|
||||
echo " ${prefix} [$i/$_WJ_POLL_MAX] curl 请求失败"
|
||||
continue
|
||||
}
|
||||
|
||||
local resp_code
|
||||
resp_code=$(_wj_json_get "$response" "code")
|
||||
|
||||
# 兼容 "Ok" 和 "OK"
|
||||
local resp_upper
|
||||
resp_upper=$(echo "$resp_code" | tr '[:lower:]' '[:upper:]')
|
||||
if [[ "$resp_upper" != "OK" && -n "$resp_upper" ]]; then
|
||||
echo " ${prefix} [$i/$_WJ_POLL_MAX] resp_code=$resp_code (非Ok)"
|
||||
continue
|
||||
fi
|
||||
|
||||
local status
|
||||
status=$(_wj_json_get_data "$response" "status")
|
||||
|
||||
local token
|
||||
token=$(_wj_json_get_data "$response" "token")
|
||||
|
||||
case "$status" in
|
||||
"completed")
|
||||
echo " ${prefix} [$i/$_WJ_POLL_MAX] status=completed ✅"
|
||||
if [[ -n "$token" ]]; then
|
||||
echo "TOKEN_READY:$token"
|
||||
return 0
|
||||
fi
|
||||
echo " ${prefix} [$i/$_WJ_POLL_MAX] ⚠️ status=completed 但 token 为空"
|
||||
echo "ERROR:empty_token"
|
||||
return 1
|
||||
;;
|
||||
"pending")
|
||||
echo " ${prefix} [$i/$_WJ_POLL_MAX] status=pending"
|
||||
continue
|
||||
;;
|
||||
*)
|
||||
echo " ${prefix} [$i/$_WJ_POLL_MAX] status=$status (未知状态)"
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "AUTH_TIMEOUT"
|
||||
return 2
|
||||
}
|
||||
|
||||
# ── 执行授权流程(第一阶段):生成链接(立即返回,不阻塞)─────────────────────
|
||||
# 输出:
|
||||
# AUTH_REQUIRED:<url> 立即输出到 stdout,同时写入 $_WJ_URL_FILE
|
||||
_wj_do_auth_start() {
|
||||
_wj_cleanup
|
||||
|
||||
# 生成授权链接(同时写入 code 和 nonce 文件)
|
||||
local auth_url
|
||||
auth_url=$(_wj_generate_auth_url)
|
||||
local rc=$?
|
||||
|
||||
# 检查生成是否成功
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
echo "$auth_url" # 透传 ERROR:xxx 消息
|
||||
return 1
|
||||
fi
|
||||
|
||||
# 将 URL 写入文件,供后续阶段读取
|
||||
_wj_safe_write "$_WJ_URL_FILE" "$auth_url" || return 1
|
||||
|
||||
# 读取 nonce 并显示
|
||||
local nonce=""
|
||||
[[ -f "$_WJ_NONCE_FILE" ]] && nonce=$(cat "$_WJ_NONCE_FILE")
|
||||
if [[ -n "$nonce" ]]; then
|
||||
echo "NONCE:$nonce"
|
||||
fi
|
||||
|
||||
# ★ 立即输出授权链接(调用方可立即展示给用户)
|
||||
echo "AUTH_REQUIRED:$auth_url"
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── 主入口函数 A:检查状态 / 生成授权链接(立即返回,不阻塞)────────────────
|
||||
#
|
||||
# AI Agent 第一步调用此函数,命令执行完毕后立即拿到输出:
|
||||
# READY 服务已就绪,直接执行用户任务,无需第二步
|
||||
# NONCE:<nonce> nonce 值(在 AUTH_REQUIRED 之前输出)
|
||||
# AUTH_REQUIRED:<url> 需要授权:立即展示链接给用户,然后执行第二步
|
||||
# ERROR:* 错误信息
|
||||
#
|
||||
wj_check_and_start_auth() {
|
||||
_wj_check_mcporter || {
|
||||
echo "ERROR:mcporter_not_found - 请先安装 Node.js 和 npm 后重试"
|
||||
return 1
|
||||
}
|
||||
|
||||
_wj_check_dependencies || return 1
|
||||
|
||||
# ★ 如果设置了 TENCENT_SURVEY_TOKEN 环境变量,直接写入配置
|
||||
if [[ -n "$TENCENT_SURVEY_TOKEN" ]]; then
|
||||
if [[ ! "$TENCENT_SURVEY_TOKEN" =~ ^wjpt_ ]]; then
|
||||
echo "ERROR:invalid_token_prefix - TENCENT_SURVEY_TOKEN 必须以 wjpt_ 开头"
|
||||
return 1
|
||||
fi
|
||||
if _wj_save_token "$TENCENT_SURVEY_TOKEN" >/dev/null 2>&1; then
|
||||
echo "READY"
|
||||
return 0
|
||||
else
|
||||
echo "ERROR:save_token_failed - Token 写入配置失败"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
_wj_check_service
|
||||
local status=$?
|
||||
|
||||
case $status in
|
||||
0)
|
||||
echo "READY"
|
||||
return 0
|
||||
;;
|
||||
1|2)
|
||||
_wj_do_auth_start || return 1
|
||||
return 0
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── 主入口函数 B:等待授权完成(阻塞,最多约 300s)────────────────────────────
|
||||
#
|
||||
# AI Agent 在展示授权链接后调用此函数,等待用户完成授权:
|
||||
# TOKEN_READY:ok 授权成功,Token 已写入配置,直接执行用户任务
|
||||
# AUTH_TIMEOUT 超时,告知用户重新发起请求
|
||||
# ERROR:* 错误信息
|
||||
#
|
||||
wj_wait_auth() {
|
||||
# 前台轮询 API 等待授权完成
|
||||
local result
|
||||
result=$(_wj_poll_token "⏳")
|
||||
local rc=$?
|
||||
|
||||
# 提取最后一行作为结构化结果
|
||||
local last_line
|
||||
last_line=$(echo "$result" | tail -1)
|
||||
|
||||
# 输出过程日志(去掉最后一行结果)
|
||||
echo "$result" | sed '$d'
|
||||
|
||||
case "$last_line" in
|
||||
TOKEN_READY:*)
|
||||
local token="${last_line#TOKEN_READY:}"
|
||||
if _wj_save_token "$token"; then
|
||||
_wj_cleanup
|
||||
echo "TOKEN_READY:ok"
|
||||
return 0
|
||||
else
|
||||
_wj_cleanup
|
||||
echo "ERROR:save_token_failed"
|
||||
return 1
|
||||
fi
|
||||
;;
|
||||
AUTH_TIMEOUT)
|
||||
_wj_cleanup
|
||||
echo "AUTH_TIMEOUT"
|
||||
return 2
|
||||
;;
|
||||
ERROR:empty_token*)
|
||||
_wj_cleanup
|
||||
echo "ERROR:empty_token - 授权异常,Token 为空"
|
||||
return 1
|
||||
;;
|
||||
ERROR:*)
|
||||
_wj_cleanup
|
||||
echo "$last_line"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
|
||||
# ── 直接执行时的交互式安装流程 ───────────────────────────────────────────────
|
||||
_wj_interactive_setup() {
|
||||
echo ""
|
||||
echo "╔══════════════════════════════════════════════╗"
|
||||
echo "║ 腾讯问卷 MCP Skill 配置向导 ║"
|
||||
echo "╚══════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
# 检查 mcporter
|
||||
echo "🔍 检查 mcporter..."
|
||||
if ! _wj_check_mcporter; then
|
||||
echo "❌ mcporter 安装失败,请先安装 Node.js (https://nodejs.org) 后重试"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ mcporter 已就绪"
|
||||
echo ""
|
||||
|
||||
# 检查系统依赖
|
||||
echo "🔍 检查系统依赖..."
|
||||
if ! _wj_check_dependencies; then
|
||||
echo "❌ 缺少必要依赖,请先安装后重试"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ 系统依赖已就绪"
|
||||
echo ""
|
||||
|
||||
# 检查服务状态
|
||||
echo "🔍 检查 tencent-survey 服务配置..."
|
||||
_wj_check_service
|
||||
local status=$?
|
||||
|
||||
case $status in
|
||||
0)
|
||||
echo "✅ tencent-survey 服务已配置且运行正常!"
|
||||
echo ""
|
||||
echo "🎉 无需重新配置,您可以直接使用腾讯问卷功能。"
|
||||
echo ""
|
||||
echo "📖 使用示例:"
|
||||
echo " mcporter call tencent-survey.get_survey --args '{\"survey_id\": 12345}'"
|
||||
return 0
|
||||
;;
|
||||
1|2)
|
||||
echo "⚠️ Token 未配置,需要授权..."
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "🔐 需要完成腾讯问卷授权"
|
||||
echo ""
|
||||
|
||||
# 清理旧状态
|
||||
_wj_cleanup
|
||||
|
||||
# 生成授权链接(同时写入 code 和 nonce 文件)
|
||||
local auth_url
|
||||
auth_url=$(_wj_generate_auth_url)
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "❌ 生成授权链接失败:$auth_url"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 读取 nonce
|
||||
local nonce=""
|
||||
[[ -f "$_WJ_NONCE_FILE" ]] && nonce=$(cat "$_WJ_NONCE_FILE")
|
||||
|
||||
echo "┌─────────────────────────────────────────────────────────┐"
|
||||
echo "│ 请在浏览器中打开以下链接完成授权: │"
|
||||
echo "│ │"
|
||||
printf "│ %s\n" "$auth_url"
|
||||
echo "│ │"
|
||||
if [[ -n "$nonce" ]]; then
|
||||
printf "│ 🔑 nonce: %s\n" "$nonce"
|
||||
echo "│ │"
|
||||
fi
|
||||
echo "│ ⚠️ 请使用 QQ 或微信 扫码 / 登录授权 │"
|
||||
echo "└─────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
echo "⏳ 正在等待您完成授权,无需任何额外操作..."
|
||||
echo " (最多等待 $((_WJ_POLL_MAX * _WJ_POLL_INTERVAL)) 秒)"
|
||||
echo ""
|
||||
|
||||
# ★ 前台轮询等待授权完成
|
||||
local result
|
||||
result=$(_wj_poll_token "🔄")
|
||||
local rc=$?
|
||||
|
||||
# 提取最后一行作为结构化结果
|
||||
local last_line
|
||||
last_line=$(echo "$result" | tail -1)
|
||||
|
||||
# 输出过程日志(去掉最后一行结果)
|
||||
echo "$result" | sed '$d'
|
||||
|
||||
case "$last_line" in
|
||||
TOKEN_READY:*)
|
||||
local token="${last_line#TOKEN_READY:}"
|
||||
echo ""
|
||||
echo "✅ 授权成功!正在保存配置..."
|
||||
if _wj_save_token "$token"; then
|
||||
_wj_cleanup
|
||||
echo "✅ Token 已写入 mcporter 配置"
|
||||
echo ""
|
||||
echo "🎉 配置完成!现在可以直接使用腾讯问卷功能了。"
|
||||
echo ""
|
||||
echo "📖 使用示例:"
|
||||
echo " mcporter call ${_WJ_SERVICE_NAME}.get_survey --args '{\"survey_id\": 12345}'"
|
||||
echo ""
|
||||
echo "🏠 腾讯问卷主页:${_WJ_API_BASE}"
|
||||
else
|
||||
# Token 写入临时文件,避免在终端明文打印
|
||||
local token_file="${_WJ_TMP_DIR}/token_backup"
|
||||
_wj_safe_write "$token_file" "$token"
|
||||
echo "⚠️ Token 写入配置失败"
|
||||
echo " Token 已保存到临时文件: $token_file"
|
||||
echo " 请手动运行:"
|
||||
echo " mcporter config add ${_WJ_SERVICE_NAME} ${_WJ_MCP_URL} --header \"Authorization=Bearer \$(cat $token_file)\" --transport http --scope home"
|
||||
fi
|
||||
;;
|
||||
AUTH_TIMEOUT)
|
||||
echo ""
|
||||
echo "⏳ 授权超时(未在时限内完成授权)"
|
||||
echo " 请重新运行:bash ./setup.sh"
|
||||
exit 1
|
||||
;;
|
||||
ERROR:*)
|
||||
echo ""
|
||||
echo "❌ 授权失败:$last_line"
|
||||
echo " 如问题持续,请联系腾讯问卷支持"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── 脚本入口 ──────────────────────────────────────────────────────────────────
|
||||
# 直接执行时:
|
||||
# bash ./setup.sh wj_check_and_start_auth → 第一步:检查状态 / 生成授权链接
|
||||
# bash ./setup.sh wj_wait_auth → 第二步:等待授权完成
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
if [[ -n "$1" ]]; then
|
||||
# 参数分发:将第一个参数作为函数名执行
|
||||
case "$1" in
|
||||
wj_check_and_start_auth|wj_wait_auth)
|
||||
"$1"
|
||||
exit $?
|
||||
;;
|
||||
setup)
|
||||
echo "🚀 腾讯问卷 MCP Skill 人工配置向导"
|
||||
echo ""
|
||||
_wj_interactive_setup
|
||||
;;
|
||||
*)
|
||||
echo "ERROR:unknown_command - 未知命令: $1"
|
||||
echo "可用命令: wj_check_and_start_auth, wj_wait_auth, setup"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "用法:"
|
||||
echo " bash ./setup.sh wj_check_and_start_auth # 第一步:检查状态 / 生成授权链接"
|
||||
echo " bash ./setup.sh wj_wait_auth # 第二步:等待授权完成"
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"display_name": "腾讯问卷",
|
||||
"display_name_en": "Tencent Survey",
|
||||
"description_zh": "腾讯问卷操作(创建、修改、逻辑设置、统计)",
|
||||
"description_en": "Tencent Survey operations (create, edit, logic settings, view statistical analysis of various surveys)\""
|
||||
}
|
||||
Reference in New Issue
Block a user