@loupekit/mcp 0.4.3 → 0.5.0-next.16
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 -0
- package/index.ts +20 -2
- package/package.json +31 -8
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ as an actionable, fully-contextualized backlog — request + element HTML + styl
|
|
|
18
18
|
|
|
19
19
|
<p>
|
|
20
20
|
<a href="https://mohamed-ashraf-elsaed.github.io/loupe/"><b>Website</b></a> ·
|
|
21
|
+
<a href="https://mohamed-ashraf-elsaed.github.io/loupe/guide/"><b>Docs</b></a> ·
|
|
21
22
|
<a href="https://github.com/mohamed-ashraf-elsaed/loupe"><b>GitHub</b></a> ·
|
|
22
23
|
<a href="https://github.com/mohamed-ashraf-elsaed/loupe/blob/main/CHANGELOG.md"><b>Changelog</b></a> ·
|
|
23
24
|
<a href="https://www.npmjs.com/package/@loupekit/sdk"><b>SDK</b></a>
|
package/index.ts
CHANGED
|
@@ -41,6 +41,12 @@ async function api(path: string, init?: RequestInit): Promise<any> {
|
|
|
41
41
|
|
|
42
42
|
const wrap = (text: string) => ({ content: [{ type: "text" as const, text }] });
|
|
43
43
|
|
|
44
|
+
/** How to name a comment's target in a one-liner. Free notes have no element. */
|
|
45
|
+
const targetOf = (c: Comment) =>
|
|
46
|
+
c.kind === "free"
|
|
47
|
+
? "page-level note"
|
|
48
|
+
: c.anchor.testid ? `[data-testid="${c.anchor.testid}"]` : c.anchor.cssPath;
|
|
49
|
+
|
|
44
50
|
// Tool handlers are exported so they can be unit-tested in-process (the stdio
|
|
45
51
|
// transport below only runs when this file is the entrypoint).
|
|
46
52
|
|
|
@@ -52,13 +58,25 @@ export async function listComments({ status, url }: { status?: string; url?: str
|
|
|
52
58
|
if (!comments.length) return wrap("No comments match.");
|
|
53
59
|
const lines = comments.map(
|
|
54
60
|
(c) =>
|
|
55
|
-
`- [${c.status}] #${c.id} — ${c.body}\n ↳ ${c
|
|
61
|
+
`- [${c.status}] #${c.id} — ${c.body}\n ↳ ${targetOf(c)} on ${c.url} (by ${c.author.name})`,
|
|
56
62
|
);
|
|
57
63
|
return wrap(`${comments.length} comment(s):\n\n${lines.join("\n")}\n\nUse get_comment(id) for the full element context.`);
|
|
58
64
|
}
|
|
59
65
|
|
|
60
66
|
export async function getComment({ id }: { id: string }) {
|
|
61
67
|
const c = (await api(`/v1/comments/${encodeURIComponent(id)}`)) as Comment;
|
|
68
|
+
// Free notes aren't tied to an element — skip the element HTML/styles sections.
|
|
69
|
+
if (c.kind === "free") {
|
|
70
|
+
return wrap(
|
|
71
|
+
[
|
|
72
|
+
`# Feedback #${c.id} from ${c.author.name} (${c.status})`,
|
|
73
|
+
``,
|
|
74
|
+
`**Note:** ${c.body}`,
|
|
75
|
+
`**Page:** ${c.url}`,
|
|
76
|
+
`**Type:** Free note — a page-level comment, not tied to a specific element (no screenshot).`,
|
|
77
|
+
].join("\n"),
|
|
78
|
+
);
|
|
79
|
+
}
|
|
62
80
|
return wrap(
|
|
63
81
|
[
|
|
64
82
|
`# Feedback #${c.id} from ${c.author.name} (${c.status})`,
|
|
@@ -86,7 +104,7 @@ export async function updateStatus({ id, status }: { id: string; status: string
|
|
|
86
104
|
return wrap(`#${id} → ${status}`);
|
|
87
105
|
}
|
|
88
106
|
|
|
89
|
-
const server = new McpServer({ name: "loupe", version: "0.
|
|
107
|
+
const server = new McpServer({ name: "loupe", version: "0.5.0" });
|
|
90
108
|
server.tool(
|
|
91
109
|
"list_comments",
|
|
92
110
|
"List Loupe product-feedback comments for the project as a task backlog. Use this to see what a PM has flagged, then work through the items.",
|
package/package.json
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loupekit/mcp",
|
|
3
|
-
"author": {
|
|
4
|
-
|
|
3
|
+
"author": {
|
|
4
|
+
"name": "Mohamed Ashraf Elsaed",
|
|
5
|
+
"email": "m.ashraf.saed@gmail.com",
|
|
6
|
+
"url": "https://www.linkedin.com/in/mohamedashrafelsaed/"
|
|
7
|
+
},
|
|
8
|
+
"version": "0.5.0-next.16",
|
|
5
9
|
"description": "MCP server that exposes Loupe comments to Claude Code as an actionable, fully-contextualized backlog.",
|
|
6
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"loupe",
|
|
12
|
+
"mcp",
|
|
13
|
+
"model-context-protocol",
|
|
14
|
+
"claude",
|
|
15
|
+
"claude-code",
|
|
16
|
+
"visual-feedback",
|
|
17
|
+
"product-management",
|
|
18
|
+
"ai"
|
|
19
|
+
],
|
|
7
20
|
"license": "MIT",
|
|
8
|
-
"repository": {
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/mohamed-ashraf-elsaed/loupe.git",
|
|
24
|
+
"directory": "packages/mcp"
|
|
25
|
+
},
|
|
9
26
|
"homepage": "https://mohamed-ashraf-elsaed.github.io/loupe/",
|
|
10
|
-
"publishConfig": {
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
11
30
|
"type": "module",
|
|
12
|
-
"files": [
|
|
13
|
-
|
|
31
|
+
"files": [
|
|
32
|
+
"index.ts"
|
|
33
|
+
],
|
|
34
|
+
"bin": {
|
|
35
|
+
"loupe-mcp": "./index.ts"
|
|
36
|
+
},
|
|
14
37
|
"scripts": {
|
|
15
38
|
"start": "node index.ts",
|
|
16
39
|
"test:client": "node test-client.ts"
|
|
17
40
|
},
|
|
18
41
|
"dependencies": {
|
|
19
|
-
"@loupekit/shared": "
|
|
42
|
+
"@loupekit/shared": "0.5.0-next.16",
|
|
20
43
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
21
44
|
"zod": "^3.24.1"
|
|
22
45
|
}
|