@usercase/mcp 0.2.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 +20 -0
- package/agents/condition.md +31 -0
- package/agents/question.md +32 -0
- package/agents/steps.md +30 -0
- package/mcp/report.js +27 -1
- package/mcp/server.js +173 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -79,6 +79,26 @@ 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
|
+
## Questions for a precise moment
|
|
83
|
+
|
|
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:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
/mcp__usercase__question
|
|
98
|
+
```
|
|
99
|
+
|
|
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.
|
|
101
|
+
|
|
82
102
|
## What the visitors will and won't do
|
|
83
103
|
|
|
84
104
|
- They identify themselves on every request (see below), and their answers are counted separately from real visitors. This cannot be turned off.
|
|
@@ -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.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Make a Usercase question
|
|
2
|
+
|
|
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.
|
|
4
|
+
|
|
5
|
+
## 1. Understand the moment
|
|
6
|
+
|
|
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.
|
|
8
|
+
|
|
9
|
+
## 2. Read the code and the visits
|
|
10
|
+
|
|
11
|
+
- Find the screens and controls involved in the code: the route patterns, the text on the buttons, field names, form classes.
|
|
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.
|
|
13
|
+
|
|
14
|
+
## 3. Write and check the condition
|
|
15
|
+
|
|
16
|
+
{{condition_rules}}
|
|
17
|
+
|
|
18
|
+
## 4. Write the question
|
|
19
|
+
|
|
20
|
+
- English, under 15 words, about this moment ("What were you looking for?"), not opinions about the site.
|
|
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.
|
|
22
|
+
|
|
23
|
+
## 5. Show the user, then save
|
|
24
|
+
|
|
25
|
+
Show the user, briefly:
|
|
26
|
+
- when it appears, in plain words, and how many recent visits matched,
|
|
27
|
+
- the question and its answers,
|
|
28
|
+
- any overlap or anything you could not express.
|
|
29
|
+
|
|
30
|
+
On their approval, call `question_save`. Give them the Inbox link it returns and tell them it stays a draft until someone turns it on there. If they want more questions, go back to step 1.
|
|
31
|
+
|
|
32
|
+
Do not change any code.
|
package/agents/steps.md
ADDED
|
@@ -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
|
@@ -183,4 +183,30 @@ const getResearchTasks = (researchId) => keyed("GET", `/agent/researches/${encod
|
|
|
183
183
|
/** 과제의 정상 경로를 통째로 바꾼다. 화면을 올리느라 오래 걸릴 수 있다 */
|
|
184
184
|
const savePath = (taskId, body) => keyed("PUT", `/agent/tasks/${encodeURIComponent(taskId)}/path`, body, 90000);
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
/** 질문 만들기(question) — 최근 방문을 읽어야 해서 조금 걸린다 */
|
|
187
|
+
const getQuestionContext = () => keyed("GET", "/agent/questions/context", null, 60000);
|
|
188
|
+
const checkQuestion = (condition) => keyed("POST", "/agent/questions/check", { condition }, 60000);
|
|
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);
|
|
193
|
+
/** 대시보드 주소. API 주소에서 /koa 를 뗀다 */
|
|
194
|
+
const dashboardUrl = (p) => apiBase().replace(/\/koa$/, "") + p;
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
KEY_ENV,
|
|
198
|
+
URL_ENV,
|
|
199
|
+
RUN_REF,
|
|
200
|
+
describeKey,
|
|
201
|
+
getInstall,
|
|
202
|
+
sendVisit,
|
|
203
|
+
sendDiagnostics,
|
|
204
|
+
getResearchTasks,
|
|
205
|
+
savePath,
|
|
206
|
+
getQuestionContext,
|
|
207
|
+
checkQuestion,
|
|
208
|
+
saveQuestion,
|
|
209
|
+
getStepsBrief,
|
|
210
|
+
sendSteps,
|
|
211
|
+
dashboardUrl,
|
|
212
|
+
};
|
package/mcp/server.js
CHANGED
|
@@ -303,6 +303,156 @@ server.registerTool(
|
|
|
303
303
|
}),
|
|
304
304
|
);
|
|
305
305
|
|
|
306
|
+
// ── 질문 만들기 (메인 에이전트, question 절차) ──
|
|
307
|
+
// 대시보드 New question 은 한 단계 조건만 고른다. 여러 단계 조건은 여기서 코드를 읽고 쓴다.
|
|
308
|
+
// 서버가 최근 방문에 돌려 본 것만 초안으로 받는다 — 켜는 건 사람이 Inbox 에서 한다
|
|
309
|
+
|
|
310
|
+
const QStep = z.object({
|
|
311
|
+
do: z.enum(["arrive", "click", "scroll", "input", "submit", "back", "hide", "error", "idle"]),
|
|
312
|
+
page: z.string().optional().describe("Path glob of the screen this happened on. * = anything, :name = one segment"),
|
|
313
|
+
from: z.string().optional().describe("arrive/back only: path glob of the screen they came from"),
|
|
314
|
+
target: z.string().optional().describe("click/input/submit: words in the element's text or field name, or class:name, or tag:BUTTON"),
|
|
315
|
+
atLeast: z.number().int().optional().describe("Happened at least this many times"),
|
|
316
|
+
percent: z.number().optional().describe("scroll: reached at least this % of the page"),
|
|
317
|
+
seconds: z.number().optional().describe("idle: a pause of at least this many seconds between two actions"),
|
|
318
|
+
cleared: z.boolean().optional().describe("input: typed and then deleted"),
|
|
319
|
+
absent: z.boolean().optional().describe("This did NOT happen between the previous step and the moment the question appears"),
|
|
320
|
+
});
|
|
321
|
+
const QCondition = z.object({
|
|
322
|
+
page: z.string().describe("Path glob of the screen the visitor is on when the question appears"),
|
|
323
|
+
steps: z.array(QStep).describe("What happened, in order. Other actions may happen in between"),
|
|
324
|
+
moment: z.object({
|
|
325
|
+
type: z.enum(["on_match", "idle", "leave"]).describe("on_match = as soon as the steps are met · idle = after that, N seconds with no action · leave = when about to leave"),
|
|
326
|
+
seconds: z.number().optional(),
|
|
327
|
+
}),
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
function describeCheck(r) {
|
|
331
|
+
const L = [`Checked against ${r.visits} visits from the last ${r.days} days: matched ${r.matched}.`];
|
|
332
|
+
if (!r.visits) L.push("There are no recent visits yet, so the condition could only be checked for shape, not against real behavior.");
|
|
333
|
+
if (r.issues.length) L.push("", "Problems — fix these before saving:", ...r.issues.map((s) => `- ${s}`));
|
|
334
|
+
else L.push("No problems found.");
|
|
335
|
+
if (r.overlaps.length)
|
|
336
|
+
L.push("", "Fires at the same moment as existing questions (only one card shows per visit, so one of them would rarely appear):", ...r.overlaps.map((o) => `- "${o.question}" (${o.status})`));
|
|
337
|
+
if (r.examples.length) L.push("", "What some matched visitors did, up to the moment the question would appear:", ...r.examples.map((e) => "```\n" + e + "\n```"));
|
|
338
|
+
L.push("", "Condition as it will be saved:", "```json", JSON.stringify(r.condition, null, 2), "```");
|
|
339
|
+
return L.join("\n");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
server.registerTool(
|
|
343
|
+
"question_context",
|
|
344
|
+
{
|
|
345
|
+
description:
|
|
346
|
+
"Screens and element words from this project's recent real visits. Call first in the question procedure, before writing a condition — page globs and click targets must match what visitors actually saw.",
|
|
347
|
+
inputSchema: {},
|
|
348
|
+
},
|
|
349
|
+
wrap(async () => {
|
|
350
|
+
const c = await report.getQuestionContext();
|
|
351
|
+
if (!c.visits) return text(`No visits in the last ${c.days} days. Conditions can still be saved, but cannot be checked against behavior until visits come in.`);
|
|
352
|
+
return text(
|
|
353
|
+
[
|
|
354
|
+
`${c.visits} visits in the last ${c.days} days.`,
|
|
355
|
+
"",
|
|
356
|
+
"Screens (visits):",
|
|
357
|
+
c.pages,
|
|
358
|
+
"",
|
|
359
|
+
"Every path seen:",
|
|
360
|
+
" " + c.paths.join(" "),
|
|
361
|
+
"",
|
|
362
|
+
"Words on clicked elements and field names, most common first:",
|
|
363
|
+
" " + c.words.map((w) => JSON.stringify(w)).join(" "),
|
|
364
|
+
].join("\n"),
|
|
365
|
+
);
|
|
366
|
+
}),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
server.registerTool(
|
|
370
|
+
"question_check",
|
|
371
|
+
{
|
|
372
|
+
description:
|
|
373
|
+
"Run a condition over this project's recent visits: how many it matches, grammar problems, overlap with existing questions, and what matched visitors did. Repeat until there are no problems.",
|
|
374
|
+
inputSchema: { condition: QCondition },
|
|
375
|
+
},
|
|
376
|
+
wrap(async ({ condition }) => text(describeCheck(await report.checkQuestion(condition)))),
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
server.registerTool(
|
|
380
|
+
"question_save",
|
|
381
|
+
{
|
|
382
|
+
description:
|
|
383
|
+
"Save the question as a DRAFT in the dashboard Inbox, after the user approved it. The server checks the condition again and refuses it if there are problems. Nobody sees it until someone turns it on.",
|
|
384
|
+
inputSchema: {
|
|
385
|
+
condition: QCondition,
|
|
386
|
+
question: z.string().describe("English, under 15 words. Asks about this moment"),
|
|
387
|
+
answerType: z.enum(["choice", "yes_no", "text"]).optional(),
|
|
388
|
+
choices: z.array(z.object({ text: z.string(), noProblem: z.boolean().optional() })).optional().describe("For choice: 2–6 choices, exactly one with noProblem: true"),
|
|
389
|
+
allowOther: z.boolean().optional().describe("Let visitors write their own answer (choice only). Default true"),
|
|
390
|
+
title: z.string().optional().describe("English, a few words, for the Inbox"),
|
|
391
|
+
why: z.string().optional().describe("English. What the team wants to learn, shown in the Inbox"),
|
|
392
|
+
triggerEn: z.string().optional().describe("English. One plain sentence: when and in what situation it appears. No paths or class names"),
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
wrap(async (args) => {
|
|
396
|
+
let r;
|
|
397
|
+
try {
|
|
398
|
+
r = await report.saveQuestion(args);
|
|
399
|
+
} catch (e) {
|
|
400
|
+
if (e.status === 422) throw new Error("The condition did not pass the check. Run question_check, fix the problems, then save again");
|
|
401
|
+
throw e;
|
|
402
|
+
}
|
|
403
|
+
const L = [`Saved as a draft — matched ${r.matched} of ${r.visits} recent visits.`, `Open it in the Inbox to turn it on: ${report.dashboardUrl(r.inboxPath)}`];
|
|
404
|
+
if (r.overlaps.length) L.push("", "Note: it fires at the same moment as:", ...r.overlaps.map((o) => `- "${o.question}" (${o.status})`));
|
|
405
|
+
return text(L.join("\n"));
|
|
406
|
+
}),
|
|
407
|
+
);
|
|
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
|
+
|
|
306
456
|
// ── 과제의 정상 경로 (메인 에이전트, path 절차) ──
|
|
307
457
|
|
|
308
458
|
const recorder = new PathRecorder(hub);
|
|
@@ -542,6 +692,29 @@ server.registerPrompt(
|
|
|
542
692
|
},
|
|
543
693
|
);
|
|
544
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
|
+
|
|
709
|
+
server.registerPrompt(
|
|
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") },
|
|
714
|
+
},
|
|
715
|
+
({ session }) => agentDoc("steps.md", { session: session || "(missing — ask the user to copy the command from the dashboard)" }),
|
|
716
|
+
);
|
|
717
|
+
|
|
545
718
|
async function main() {
|
|
546
719
|
// 예전에 깐 방문자 에이전트가 있으면 이 버전 것으로 바꿔 둔다 — setup 은 처음 한 번만 불려서 옛 지시문이 남는다.
|
|
547
720
|
// 리서치를 한 번도 안 쓴 사람에게는 만들지 않는다
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usercase/mcp",
|
|
3
|
-
"version": "0.2.
|
|
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",
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"check": "node --check mcp/server.js && node scripts/onboarding.js --check && node scripts/agents.js --check",
|
|
29
|
-
"docs": "node scripts/onboarding.js && node scripts/agents.js"
|
|
29
|
+
"docs": "node scripts/onboarding.js && node scripts/agents.js",
|
|
30
|
+
"release:next": "node scripts/release.js next",
|
|
31
|
+
"release:promote": "node scripts/release.js promote",
|
|
32
|
+
"release:rollback": "node scripts/release.js rollback",
|
|
33
|
+
"release:status": "node scripts/release.js status"
|
|
30
34
|
},
|
|
31
35
|
"dependencies": {
|
|
32
36
|
"@modelcontextprotocol/sdk": "^1.30.0",
|