@usercase/mcp 0.2.8-next.2 → 0.2.8-next.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agents/new-research.md +21 -4
- package/mcp/path.js +18 -4
- package/mcp/report.js +3 -0
- package/mcp/server.js +25 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,7 +109,7 @@ Claude suggests the moment, the question and its answers, and saves it only afte
|
|
|
109
109
|
/mcp__usercase__new-research
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
Claude asks whether you already know what you want to learn. If you don't, it reads your project's findings and where visitors left, and suggests two or three researches, saying which evidence comes from real visitors and which from virtual users. With no data yet, it offers to send a few virtual users first. It then reads your code to write the tasks and their success conditions (the page that means done, or the exact button), and saves the research only after you approve it — as a draft in your Inbox, where you create it.
|
|
112
|
+
Claude asks whether you already know what you want to learn. If you don't, it reads your project's findings and where visitors left, and suggests two or three researches, saying which evidence comes from real visitors and which from virtual users. With no data yet, it offers to send a few virtual users first. It then reads your code to write the tasks and their success conditions (the page that means done, or the exact button), and saves the research only after you approve it — as a draft in your Inbox, where you create it. It then offers to walk each task in a browser and record its expected path; the paths stay with the draft and move into the research when you create it. A draft you discard, or don't create within 30 days, loses its recorded paths and their screenshots.
|
|
113
113
|
|
|
114
114
|
## What the visitors will and won't do
|
|
115
115
|
|
package/agents/new-research.md
CHANGED
|
@@ -56,14 +56,31 @@ One research is **one flow a participant can walk in one sitting**: 1–4 tasks,
|
|
|
56
56
|
|
|
57
57
|
**Invite**: the page glob where visitors should be invited (where the flow begins, or the moment the evidence points to), and one short line asking them to take part.
|
|
58
58
|
|
|
59
|
+
**Wording people read** (the start screen, the invite line):
|
|
60
|
+
- Say plainly what they will do and roughly how long it takes.
|
|
61
|
+
- The participant is the subject. Never ask for help, never make the team or the site the subject, never thank in advance.
|
|
62
|
+
- Do not mention the problem you suspect or what others said.
|
|
63
|
+
|
|
59
64
|
**Language**: English, unless the user asks for another language.
|
|
60
65
|
|
|
61
66
|
## 6. Show the user, then save
|
|
62
67
|
|
|
63
|
-
Show the whole research compactly: the purpose and its evidence, each task with how it counts as done (in plain words
|
|
68
|
+
Show the whole research compactly: the purpose and its evidence, each task with how it counts as done (in plain words, no paths or XPath), the questions, and the invite. Change whatever they ask for.
|
|
69
|
+
|
|
70
|
+
On their approval, call `research_save` with `basis` (`real` if the evidence came from real visitors, `virtual` if only from virtual users, `none` otherwise) and the ids of the findings it answers. Give them the Inbox link it returns and tell them it stays a draft until they create it in the Inbox, where they also set when the invite appears.
|
|
71
|
+
|
|
72
|
+
## 7. Record each task's expected path
|
|
73
|
+
|
|
74
|
+
The results tell who went straight from who took a detour only when a task has an expected path, and walking it in a browser confirms the success condition you wrote from the code. Ask the user whether to record the paths now. If they say no, tell them `{{path_cmd}}` with the research id does it after the research is created, and stop.
|
|
75
|
+
|
|
76
|
+
For each task with a page or click to reach (skip tasks that are done by staying on a page), use the task id `research_save` listed for it, `draft:<draft>:<number>`:
|
|
77
|
+
|
|
78
|
+
{{path_walk}}
|
|
79
|
+
|
|
80
|
+
### Show the user, then save
|
|
81
|
+
|
|
82
|
+
Show the user the steps and the screenshots `path_check` lists. Ask whether this is the route they intend, and whether the task should be done on reaching the last page (`page`), on the last recorded click (`click`), or keep the condition you wrote (`keep`). With page or click the path ends at that point — say so when the route goes on after it.
|
|
64
83
|
|
|
65
|
-
On their approval, call `
|
|
66
|
-
- it stays a draft until they create it in the Inbox, where they also set when the invite appears;
|
|
67
|
-
- after it is created, `{{path_cmd}}` with the research id records each task's expected path, so the results can tell who went straight from who took a detour.
|
|
84
|
+
On their approval, call `path_save`. The path stays in the draft and moves into the research when it is created; a draft that is discarded, or not created within 30 days, loses its paths. When all tasks are done, tell the user which have a path and any you could not record and why.
|
|
68
85
|
|
|
69
86
|
Do not change any code.
|
package/mcp/path.js
CHANGED
|
@@ -150,6 +150,12 @@ class PathRecorder {
|
|
|
150
150
|
.join("\n");
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
/** 방금 저장한 리서치 초안의 과제들(new-research) — draft:<초안>:<번호> 로 path_start 에 쓴다 */
|
|
154
|
+
addDraft(draftId, { title, website, tasks }) {
|
|
155
|
+
const research = { title, status: "draft", website };
|
|
156
|
+
for (const t of tasks) this.known.set(`draft:${draftId}:${t.index}`, { research, task: { taskId: `draft:${draftId}:${t.index}`, title: t.title } });
|
|
157
|
+
}
|
|
158
|
+
|
|
153
159
|
async open(s, { device, account }) {
|
|
154
160
|
let storageState;
|
|
155
161
|
if (account) {
|
|
@@ -164,7 +170,12 @@ class PathRecorder {
|
|
|
164
170
|
/** 새로 기록을 시작한다. 같은 과제를 다시 하면 처음부터 */
|
|
165
171
|
async start({ task, url, device = "desktop", account = null }) {
|
|
166
172
|
const k = this.known.get(String(task));
|
|
167
|
-
if (!k)
|
|
173
|
+
if (!k)
|
|
174
|
+
throw new Error(
|
|
175
|
+
String(task).startsWith("draft:")
|
|
176
|
+
? `Draft task ${task} is not known in this session. Draft tasks are known right after research_save; once the research is created, use path_tasks with its id instead.`
|
|
177
|
+
: `Task ${task} is not known yet. Call path_tasks with the research id first.`,
|
|
178
|
+
);
|
|
168
179
|
const full = /^https?:\/\//i.test(url) ? url : new URL(url, k.research.website || undefined).toString();
|
|
169
180
|
await this.stop();
|
|
170
181
|
const s = this.hub.site(full);
|
|
@@ -407,10 +418,13 @@ class PathRecorder {
|
|
|
407
418
|
steps: steps.map((s) => ({ type: s.type, url: s.url, xpath: s.xpath || "", classes: s.classes || [], ids: s.ids || [], fingerprint: s.fingerprint || null, image: fs.readFileSync(s.image).toString("base64") })),
|
|
408
419
|
};
|
|
409
420
|
if (success === "page") body.success = { condition: "url_entry", targetUrl: last.url };
|
|
410
|
-
else if (success === "click") body.success = { condition: "element_click", xpath: last.xpath, xpathUrl: last.url, classes: last.classes, ids: last.ids };
|
|
411
|
-
|
|
421
|
+
else if (success === "click") body.success = { condition: "element_click", xpath: last.xpath, xpathUrl: last.url, classes: last.classes, ids: last.ids, label: last.label || "" };
|
|
422
|
+
// 초안 과제(new-research, draft:<초안>:<번호>)는 초안에 둔다 — 리서치로 만들 때 옮겨진다
|
|
423
|
+
const draft = c.taskId.match(/^draft:([^:]+):(\d+)$/);
|
|
424
|
+
const r = draft ? await report.saveDraftPath(draft[1], Number(draft[2]), body) : await report.savePath(c.taskId, body);
|
|
425
|
+
const where = draft ? "The path is kept in the draft and moves into the research when it is created." : "The dashboard's task results now separate going straight from detours.";
|
|
412
426
|
const done = [
|
|
413
|
-
`Saved the expected path for task ${c.taskId} ("${c.title}"): ${r.steps} steps${r.success ? `, success condition set to ${r.success === "url_entry" ? `reaching ${last.url}` : `clicking "${last.label || last.tag}"`}` : ""}.
|
|
427
|
+
`Saved the expected path for task ${c.taskId} ("${c.title}"): ${r.steps} steps${r.success ? `, success condition set to ${r.success === "url_entry" ? `reaching ${last.url}` : `clicking "${last.label || last.tag}"`}` : ""}. ${where}`,
|
|
414
428
|
cut.length
|
|
415
429
|
? `Left out the ${cut.length} step(s) after the success point (${cut.map((s) => (s.type === "URL" ? `page ${s.url}` : `click "${s.label || s.tag}"`)).join(", ")}) — the task ends at the success point, so no participant could ever reach them. Tell the user.`
|
|
416
430
|
: "",
|
package/mcp/report.js
CHANGED
|
@@ -279,6 +279,8 @@ const getResearchTasks = (researchId) => keyed("GET", `/agent/researches/${encod
|
|
|
279
279
|
|
|
280
280
|
/** 과제의 정상 경로를 통째로 바꾼다. 화면을 올리느라 오래 걸릴 수 있다 */
|
|
281
281
|
const savePath = (taskId, body) => keyed("PUT", `/agent/tasks/${encodeURIComponent(taskId)}/path`, body, 90000);
|
|
282
|
+
/** 아직 리서치가 안 된 초안 과제의 경로(new-research) — 초안에 둔다 */
|
|
283
|
+
const saveDraftPath = (draftId, index, body) => keyed("PUT", `/agent/researches/drafts/${encodeURIComponent(draftId)}/tasks/${index}/path`, body, 90000);
|
|
282
284
|
|
|
283
285
|
/** 질문 만들기(question) — 최근 방문을 읽어야 해서 조금 걸린다 */
|
|
284
286
|
const getQuestionContext = () => keyed("GET", "/agent/questions/context", null, 60000);
|
|
@@ -305,6 +307,7 @@ module.exports = {
|
|
|
305
307
|
sendDiagnostics,
|
|
306
308
|
getResearchTasks,
|
|
307
309
|
savePath,
|
|
310
|
+
saveDraftPath,
|
|
308
311
|
getQuestionContext,
|
|
309
312
|
checkQuestion,
|
|
310
313
|
saveQuestion,
|
package/mcp/server.js
CHANGED
|
@@ -713,10 +713,20 @@ server.registerTool(
|
|
|
713
713
|
},
|
|
714
714
|
wrap(async (args) => {
|
|
715
715
|
const r = await report.saveResearchDraft(args);
|
|
716
|
+
// 과제 경로 녹화(path_start)에 쓸 번호 — 옛 서버는 tasks 를 안 준다
|
|
717
|
+
const tasks = r.tasks || [];
|
|
718
|
+
if (tasks.length) recorder.addDraft(r.draftId, { title: args.title, website: r.website || "", tasks });
|
|
716
719
|
return text(
|
|
717
720
|
[
|
|
718
721
|
`Saved as a draft${r.findings ? `, linked to ${r.findings} finding${r.findings > 1 ? "s" : ""}` : ""}.`,
|
|
719
722
|
`Open it in the Inbox to review and create it: ${report.dashboardUrl(r.inboxPath)}`,
|
|
723
|
+
...(tasks.length
|
|
724
|
+
? [
|
|
725
|
+
"",
|
|
726
|
+
`Task ids for recording their expected paths with path_start${r.website ? ` (relative URLs resolve against ${r.website})` : ""}:`,
|
|
727
|
+
...tasks.map((t) => `- draft:${r.draftId}:${t.index} — ${t.title}`),
|
|
728
|
+
]
|
|
729
|
+
: []),
|
|
720
730
|
].join("\n"),
|
|
721
731
|
);
|
|
722
732
|
}),
|
|
@@ -843,6 +853,19 @@ const agentDoc = (name, vars = {}) => {
|
|
|
843
853
|
return body;
|
|
844
854
|
};
|
|
845
855
|
|
|
856
|
+
/** path.md 의 "## 3. Walk it" ~ "## 4. Check it" 끝까지 — 제목은 한 단계 낮추고 번호를 뗀다 */
|
|
857
|
+
const pathWalkRules = () => {
|
|
858
|
+
const d = doc("path.md");
|
|
859
|
+
const from = d.indexOf("## 3. Walk it");
|
|
860
|
+
const to = d.indexOf("## 5.");
|
|
861
|
+
if (from < 0 || to < 0) throw new Error("agents/path.md lost its Walk it / Check it sections");
|
|
862
|
+
return d
|
|
863
|
+
.slice(from, to)
|
|
864
|
+
.replace(/^## \d+\. /gm, "### ")
|
|
865
|
+
.replace(/\n*Do not change any code\.\n*/g, "\n\n")
|
|
866
|
+
.trim();
|
|
867
|
+
};
|
|
868
|
+
|
|
846
869
|
/**
|
|
847
870
|
* 명령 지시문. 따로 쓰면 MCP 프롬프트(/mcp__usercase__research), 플러그인이면 instructions 도구(/usercase:research 스킬이 부른다)로
|
|
848
871
|
* 같은 글이 나간다
|
|
@@ -910,7 +933,8 @@ const COMMANDS = {
|
|
|
910
933
|
// 리서치 초안 — 무엇을 알고 싶은지는 대화로 묻는다(인자 없음, question 과 같은 이유)
|
|
911
934
|
"new-research": {
|
|
912
935
|
description: "Plan a Usercase research from your data and code — your agent suggests what to research, writes the tasks and questions, and saves a draft",
|
|
913
|
-
|
|
936
|
+
// 경로를 걷고 확인하는 규칙은 path 명령과 한 벌 — path.md 의 3·4 단계를 그대로 싣는다
|
|
937
|
+
body: () => agentDoc("new-research.md", { research_cmd: cmd("research"), path_cmd: cmd("path"), path_walk: pathWalkRules() }),
|
|
914
938
|
},
|
|
915
939
|
// 프로젝트 바꾸기 — 연결은 저장소 폴더마다라 이 저장소만 바뀐다
|
|
916
940
|
connect: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usercase/mcp",
|
|
3
|
-
"version": "0.2.8-next.
|
|
3
|
+
"version": "0.2.8-next.3",
|
|
4
4
|
"description": "Virtual users that browse your website and tell you what they thought — an MCP server for Claude Code",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"homepage": "https://usercase.io",
|