@usercase/mcp 0.2.1-next.0 → 0.2.1-next.1

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 CHANGED
@@ -79,15 +79,25 @@ A research's task results separate people who **went straight** from those who *
79
79
 
80
80
  Claude reads your code to work out the route each task is meant to take, tells you the route, and walks it in a browser. It then walks it again in a fresh browser to make sure every step is found the same way, shows you the screenshots, and saves the path only after you approve it. Picking one item from a list (a product, a post) is not tied to the item Claude happened to open — any item of that kind counts. A local dev server is fine; only page paths are saved.
81
81
 
82
- ## Make a question for a precise moment
82
+ ## Questions for a precise moment
83
83
 
84
- The dashboard's **New question** picks one simple moment (on arrival, after a pause, when leaving, after a click). For anything more precise, ask from your repository:
84
+ The dashboard's **New question** picks one simple moment (on arrival, after a pause, when leaving, after a click). For a moment made of several steps — "typed a search, opened no result, then did nothing for 10 seconds" — Claude writes it from your code and checks it against your recent real visits. Both need the API key.
85
+
86
+ **You wrote the question, and want it to appear at a precise moment.** In New question, choose **Several steps**, describe the moment, and copy the command it gives you:
87
+
88
+ ```
89
+ /mcp__usercase__steps <window id>
90
+ ```
91
+
92
+ Claude reads what you wrote, writes the steps, and sends them back to that window. You see them there, and save the question as usual.
93
+
94
+ **You're not sure what to ask.** Run this and tell Claude who you want to hear from:
85
95
 
86
96
  ```
87
- /mcp__usercase__question people who search but don't open any result for 10 seconds
97
+ /mcp__usercase__question
88
98
  ```
89
99
 
90
- Claude reads your code for the real routes and button labels, writes the moment as a series of steps, and runs it over your recent real visits to show how many it matches and what those visitors did. It shows you the question and its answers, and saves it only after you approve — as a draft in your Inbox. Nobody sees it until you turn it on there. Needs the API key.
100
+ Claude suggests the moment, the question and its answers, and saves it only after you approve — as a draft in your Inbox. Nobody sees it until you turn it on there.
91
101
 
92
102
  ## What the visitors will and won't do
93
103
 
@@ -0,0 +1,31 @@
1
+ A condition has a `page` (the screen the visitor is on when the card appears), `steps` in order, and a `moment`.
2
+
3
+ Step kinds (`do`): `arrive` · `back` · `click` · `input` · `submit` · `scroll` · `idle` · `hide` (tab hidden) · `error` (a JavaScript error).
4
+ Options on a step: `page`, `from` (arrive/back), `target` (click/input/submit), `atLeast`, `percent` (scroll), `seconds` (idle: a pause between two actions), `cleared` (input: typed then deleted), `absent` (this did not happen between the previous step and the moment).
5
+ Moment: `on_match` (as soon as the steps are met) · `idle` with `seconds` (then that long with no action) · `leave` (about to leave; on phones, a long pause instead).
6
+
7
+ Globs: `*` matches anything including `/`; `:name` matches one path segment (`/project/:slug`). Use the route parameter names from the code. Never put `?` or `#` in a glob.
8
+
9
+ Targets match the element's own text, field name or class — case does not matter, and the words only need to be contained. Prefer the words the visitor saw. Use `class:name` for buttons without text and `tag:BUTTON` only when the text differs every time (a list of names).
10
+
11
+ Steps happen in order, with anything allowed in between. All steps must happen; there is no "or" — two situations are two questions.
12
+
13
+ **It cannot see:** what is on the screen (an error message, an empty list, a price), the values typed (only length and whether it was cleared), time limits ("within 10 seconds"), who the visitor is (device, new or returning, campaign), or anything across visits. A visit is one tab; 30 minutes of nothing starts a new one. If the user asks for one of these, say so and offer the nearest thing it can do — usually a behavior that goes with it (a submit followed by no arrival at the next page, instead of "an error was shown").
14
+
15
+ Rules that make a card actually appear and mean something:
16
+ - The card must be shown on the screen where the trouble is. A click that opens another page ends the situation — use the `arrive` on the next page instead.
17
+ - Never ask while they are typing: the last step of an `on_match` condition must not be `input`. Use `idle` or the next step.
18
+ - Never ask the moment someone lands on a sign-in, sign-up or payment screen; use `idle` or `leave` there.
19
+ - The page must name a real screen, not only wildcards.
20
+ - Narrow enough to be one situation: a condition that matches a large share of all visits is too broad.
21
+
22
+ ### Checking it
23
+
24
+ Call `question_check` with the condition. Read the result:
25
+ - **Problems**: fix them and check again. The server refuses the condition while there are any.
26
+ - **Matched 0** with visits present: the paths or words are wrong, or the situation has not happened recently. Compare with `question_context`.
27
+ - **Examples**: read what the matched visitors did. If it is not the situation the user meant, the condition is wrong even if it passes.
28
+ - **Overlaps**: an existing question fires at the same moment; only one card shows per visit. Tell the user.
29
+ - `idle` and `leave` moments are not counted by the check — it counts visits where the steps happened. Fewer people will actually see the card.
30
+
31
+ `triggerEn`: one plain sentence a product manager understands, saying when the card appears. No paths or class names.
@@ -1,56 +1,26 @@
1
1
  # Make a Usercase question
2
2
 
3
- What the user wants: {{ask}}
4
-
5
3
  Usercase shows a small question card to real visitors of this site at a chosen moment, and the answers show the team why people get stuck. You write **when** the card appears (a condition), the question and its answers. The dashboard can only make one-step conditions; you can chain steps because **you may read the code** — the routes, the components, the labels on buttons.
6
4
 
7
5
  ## 1. Understand the moment
8
6
 
9
- From the user's words, decide what situation they mean: which screen, what the visitor did before, and when it is fair to interrupt. If the request is vague ("ask people on checkout"), ask one short question to pin the moment down. Otherwise go on.
7
+ Ask the user, in one short question, who they want to hear from and what they want to learn — unless they already said so. Then decide what situation they mean: which screen, what the visitor did before, and when it is fair to interrupt. If it is still vague ("ask people on checkout"), ask one more short question to pin the moment down.
10
8
 
11
9
  ## 2. Read the code and the visits
12
10
 
13
11
  - Find the screens and controls involved in the code: the route patterns, the text on the buttons, field names, form classes.
14
12
  - Call `question_context`. It lists the paths and element words real visitors produced recently. **Page globs and targets must match these.** The code says what exists; the visits say what is actually used and how it is labelled at runtime. If they disagree, trust the visits.
15
13
 
16
- ## 3. Write the condition
17
-
18
- A condition has a `page` (the screen the visitor is on when the card appears), `steps` in order, and a `moment`.
19
-
20
- Step kinds (`do`): `arrive` · `back` · `click` · `input` · `submit` · `scroll` · `idle` · `hide` (tab hidden) · `error` (a JavaScript error).
21
- Options on a step: `page`, `from` (arrive/back), `target` (click/input/submit), `atLeast`, `percent` (scroll), `seconds` (idle: a pause between two actions), `cleared` (input: typed then deleted), `absent` (this did not happen between the previous step and the moment).
22
- Moment: `on_match` (as soon as the steps are met) · `idle` with `seconds` (then that long with no action) · `leave` (about to leave; on phones, a long pause instead).
23
-
24
- Globs: `*` matches anything including `/`; `:name` matches one path segment (`/project/:slug`). Use the route parameter names from the code. Never put `?` or `#` in a glob.
25
-
26
- Targets match the element's own text, field name or class — case does not matter, and the words only need to be contained. Prefer the words the visitor saw. Use `class:name` for buttons without text and `tag:BUTTON` only when the text differs every time (a list of names).
27
-
28
- Steps happen in order, with anything allowed in between. All steps must happen; there is no "or" — two situations are two questions.
29
-
30
- **It cannot see:** what is on the screen (an error message, an empty list, a price), the values typed (only length and whether it was cleared), time limits ("within 10 seconds"), who the visitor is (device, new or returning, campaign), or anything across visits. A visit is one tab; 30 minutes of nothing starts a new one. If the user asks for one of these, say so and offer the nearest thing it can do — usually a behavior that goes with it (a submit followed by no arrival at the next page, instead of "an error was shown").
31
-
32
- Rules that make a card actually appear and mean something:
33
- - The card must be shown on the screen where the trouble is. A click that opens another page ends the situation — use the `arrive` on the next page instead.
34
- - Never ask while they are typing: the last step of an `on_match` condition must not be `input`. Use `idle` or the next step.
35
- - Never ask the moment someone lands on a sign-in, sign-up or payment screen; use `idle` or `leave` there.
36
- - The page must name a real screen, not only wildcards.
37
- - Narrow enough to be one situation: a condition that matches a large share of all visits is too broad.
38
-
39
- ## 4. Check it
14
+ ## 3. Write and check the condition
40
15
 
41
- Call `question_check` with the condition. Read the result:
42
- - **Problems**: fix them and check again. Saving is refused while there are any.
43
- - **Matched 0** with visits present: the paths or words are wrong, or the situation has not happened recently. Compare with `question_context`.
44
- - **Examples**: read what the matched visitors did. If it is not the situation the user meant, the condition is wrong even if it passes.
45
- - **Overlaps**: an existing question fires at the same moment; only one card shows per visit. Tell the user.
16
+ {{condition_rules}}
46
17
 
47
- ## 5. Write the question
18
+ ## 4. Write the question
48
19
 
49
20
  - English, under 15 words, about this moment ("What were you looking for?"), not opinions about the site.
50
21
  - Prefer `choice` with 3–5 short answers in the visitor's own words, one of them meaning nothing was wrong, marked `noProblem: true` — when most people pick it, the question turns itself off. Use `text` only when you genuinely cannot guess the likely answers. Avoid `yes_no`: it says that something is wrong, not what.
51
- - `triggerEn`: one plain sentence a product manager understands, saying when it appears. No paths or class names.
52
22
 
53
- ## 6. Show the user, then save
23
+ ## 5. Show the user, then save
54
24
 
55
25
  Show the user, briefly:
56
26
  - when it appears, in plain words, and how many recent visits matched,
@@ -0,0 +1,30 @@
1
+ # Write the steps for a Usercase question
2
+
3
+ Window: {{session}}
4
+
5
+ The user is writing a question in the Usercase dashboard (New question → Several steps) and wants it to appear at a moment made of several steps. The question itself is theirs. **You only write when it appears** — a condition — and send it back to that window, where they see it and save the question.
6
+
7
+ You may read the code: the routes, the components, the labels on buttons.
8
+
9
+ ## 1. Read what they wrote
10
+
11
+ Call `steps_brief` with the window id. It gives the moment they described, the page they typed under Where (if any) and their question. If the window id is missing or unknown, ask the user to choose Several steps again and copy the new command.
12
+
13
+ If the description is vague, ask one short question to pin the moment down. Otherwise go on.
14
+
15
+ ## 2. Read the code and the visits
16
+
17
+ - Find the screens and controls involved in the code: the route patterns, the text on the buttons, field names, form classes.
18
+ - Call `question_context`. It lists the paths and element words real visitors produced recently. **Page globs and targets must match these.** The code says what exists; the visits say what is actually used and how it is labelled at runtime. If they disagree, trust the visits.
19
+
20
+ ## 3. Write and check the condition
21
+
22
+ {{condition_rules}}
23
+
24
+ Make sure the moment fits their question: the card should appear when the visitor can answer it.
25
+
26
+ ## 4. Send it back
27
+
28
+ Tell the user in plain words when it will appear and how many recent visits matched, and anything you could not express. Then call `steps_send` with the window id, the condition and `triggerEn`. The steps appear in their dashboard window right away; they save the question there. Do not save a question yourself.
29
+
30
+ Do not change any code.
package/mcp/report.js CHANGED
@@ -187,6 +187,9 @@ const savePath = (taskId, body) => keyed("PUT", `/agent/tasks/${encodeURICompone
187
187
  const getQuestionContext = () => keyed("GET", "/agent/questions/context", null, 60000);
188
188
  const checkQuestion = (condition) => keyed("POST", "/agent/questions/check", { condition }, 60000);
189
189
  const saveQuestion = (body) => keyed("POST", "/agent/questions", body, 60000);
190
+ /** New question 창(Several steps) — 창에 쓴 것을 읽고, 조건만 돌려보낸다 */
191
+ const getStepsBrief = (session) => keyed("GET", `/agent/questions/steps/${encodeURIComponent(session)}`);
192
+ const sendSteps = (session, body) => keyed("PUT", `/agent/questions/steps/${encodeURIComponent(session)}`, body, 60000);
190
193
  /** 대시보드 주소. API 주소에서 /koa 를 뗀다 */
191
194
  const dashboardUrl = (p) => apiBase().replace(/\/koa$/, "") + p;
192
195
 
@@ -203,5 +206,7 @@ module.exports = {
203
206
  getQuestionContext,
204
207
  checkQuestion,
205
208
  saveQuestion,
209
+ getStepsBrief,
210
+ sendSteps,
206
211
  dashboardUrl,
207
212
  };
package/mcp/server.js CHANGED
@@ -406,6 +406,53 @@ server.registerTool(
406
406
  }),
407
407
  );
408
408
 
409
+ // New question 창(Several steps)이 기다리는 조건 — 질문 글은 사람 것이고 여기서는 조건만 보낸다
410
+ server.registerTool(
411
+ "steps_brief",
412
+ {
413
+ description: "What the user wrote in their New question window: the moment they described, the page under Where, and their question. Call first in the steps procedure.",
414
+ inputSchema: { session: z.string().describe("The window id from the dashboard command") },
415
+ },
416
+ wrap(async ({ session }) => {
417
+ const b = await report.getStepsBrief(session);
418
+ return text(
419
+ [
420
+ `Moment they described: ${b.moment || "(nothing — ask the user)"}`,
421
+ `Where: ${b.where || "(empty)"}`,
422
+ `Their question: ${b.question || "(not written yet)"}`,
423
+ b.sent ? "Steps were already sent to this window once; sending again replaces them." : "",
424
+ ]
425
+ .filter(Boolean)
426
+ .join("\n"),
427
+ );
428
+ }),
429
+ );
430
+
431
+ server.registerTool(
432
+ "steps_send",
433
+ {
434
+ description:
435
+ "Send the condition back to the user's New question window, after question_check shows no problems. The server checks it again and refuses it if there are problems. The user saves the question there.",
436
+ inputSchema: {
437
+ session: z.string().describe("The window id from the dashboard command"),
438
+ condition: QCondition,
439
+ triggerEn: z.string().describe("English. One plain sentence: when and in what situation it appears. No paths or class names"),
440
+ },
441
+ },
442
+ wrap(async ({ session, condition, triggerEn }) => {
443
+ let r;
444
+ try {
445
+ r = await report.sendSteps(session, { condition, triggerEn });
446
+ } catch (e) {
447
+ if (e.status === 422) throw new Error("The condition did not pass the check. Run question_check, fix the problems, then send again");
448
+ throw e;
449
+ }
450
+ const L = [`Sent — the steps are now in the user's New question window (matched ${r.matched} of ${r.visits} recent visits). They save the question there.`];
451
+ if (r.overlaps.length) L.push("", "Note: it fires at the same moment as:", ...r.overlaps.map((o) => `- "${o.question}" (${o.status})`));
452
+ return text(L.join("\n"));
453
+ }),
454
+ );
455
+
409
456
  // ── 과제의 정상 경로 (메인 에이전트, path 절차) ──
410
457
 
411
458
  const recorder = new PathRecorder(hub);
@@ -645,16 +692,27 @@ server.registerPrompt(
645
692
  },
646
693
  );
647
694
 
695
+ /** 조건 쓰는 규칙은 question·steps 가 같이 쓴다 — 한 벌만 둔다 */
696
+ const agentDoc = (name, vars = {}) => {
697
+ let body = fs.readFileSync(path.join(__dirname, "..", "agents", name), "utf8").replace(/\r\n/g, "\n");
698
+ const rules = fs.readFileSync(path.join(__dirname, "..", "agents", "condition.md"), "utf8").replace(/\r\n/g, "\n").trim();
699
+ body = body.replace("{{condition_rules}}", rules);
700
+ for (const [k, v] of Object.entries(vars)) body = body.replace(`{{${k}}}`, v);
701
+ return { messages: [{ role: "user", content: { type: "text", text: body } }] };
702
+ };
703
+
704
+ // 인자를 두지 않는다 — Claude Code 는 명령어 인자를 공백으로 나눠 문장을 못 싣는다. 무엇을 물을지는 대화로 묻는다
705
+ server.registerPrompt("question", { description: "Make a Usercase question for a precise moment on your site — Claude suggests the moment, question and answers" }, () =>
706
+ agentDoc("question.md"),
707
+ );
708
+
648
709
  server.registerPrompt(
649
- "question",
650
- { description: "Make a Usercase question that appears at a precise moment on your site", argsSchema: { ask: z.string().optional().describe("Who to ask and what to learn, in your own words") } },
651
- ({ ask }) => {
652
- const body = fs
653
- .readFileSync(path.join(__dirname, "..", "agents", "question.md"), "utf8")
654
- .replace(/\r\n/g, "\n")
655
- .replace("{{ask}}", ask || "(ask the user what moment they want to ask about, and what they want to learn)");
656
- return { messages: [{ role: "user", content: { type: "text", text: body } }] };
710
+ "steps",
711
+ {
712
+ description: "Write the steps for the question open in your dashboard (New question → Several steps)",
713
+ argsSchema: { session: z.string().optional().describe("The window id shown in the dashboard command") },
657
714
  },
715
+ ({ session }) => agentDoc("steps.md", { session: session || "(missing — ask the user to copy the command from the dashboard)" }),
658
716
  );
659
717
 
660
718
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usercase/mcp",
3
- "version": "0.2.1-next.0",
3
+ "version": "0.2.1-next.1",
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",