@web-remarq/mcp 0.1.0 → 0.2.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 +106 -7
- package/dist/server.js +251 -16
- package/dist/server.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
MCP server for [web-remarq](https://github.com/DPostnik/web-remarq) — gives AI
|
|
4
4
|
agents (Claude Code, Cursor, Codex, Windsurf) direct access to project
|
|
5
|
-
annotations
|
|
5
|
+
annotations. Two modes: **local** (zero-config, file-backed, no account
|
|
6
|
+
needed) and **cloud** (Supabase-backed via `@web-remarq/cloud`, for team sync).
|
|
6
7
|
|
|
7
8
|
## What it does
|
|
8
9
|
|
|
@@ -11,13 +12,104 @@ annotations stored in the cloud backend (`@web-remarq/cloud`).
|
|
|
11
12
|
for the annotated element and grep-friendly search hints
|
|
12
13
|
- Drives the lifecycle: `acknowledge` (pending → in-progress), `claim_fix`
|
|
13
14
|
(→ fixed_unverified), `dismiss` (with optional reason)
|
|
15
|
+
- `watch_annotations` long-polls for new pending feedback, so an agent can sit
|
|
16
|
+
in a loop and react as a designer annotates
|
|
14
17
|
- All MCP-driven changes are recorded as `actor: 'agent'` in the annotation's
|
|
15
18
|
lifecycle history, visible in the widget's History viewer
|
|
16
19
|
|
|
17
20
|
`verify` and `reject` are **not** exposed — verification is human-only via the
|
|
18
21
|
browser widget, by design (core v0.7.0 verification gate).
|
|
19
22
|
|
|
20
|
-
##
|
|
23
|
+
## Local mode (no Supabase)
|
|
24
|
+
|
|
25
|
+
Run with no `REMARQ_*` cloud env vars set and the server starts in local mode
|
|
26
|
+
automatically:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"web-remarq": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "@web-remarq/mcp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Annotations are stored in a JSON file on disk and served to the widget over a
|
|
40
|
+
small HTTP endpoint on `127.0.0.1`. No Supabase project, no project key.
|
|
41
|
+
|
|
42
|
+
Trust model: the endpoint binds to `127.0.0.1` with permissive CORS, so any
|
|
43
|
+
page open in the local browser can reach it for as long as the server runs -
|
|
44
|
+
this is meant for dev-time use with low-sensitivity data, not a hardened
|
|
45
|
+
local API.
|
|
46
|
+
|
|
47
|
+
| Env var | Default | Purpose |
|
|
48
|
+
|---------|---------|---------|
|
|
49
|
+
| `REMARQ_PORT` | `1817` | Port for the widget-facing HTTP endpoint |
|
|
50
|
+
| `REMARQ_DATA_FILE` | `.remarq/annotations.json` | Where annotations are persisted |
|
|
51
|
+
|
|
52
|
+
`.remarq/` self-gitignores on first write - nothing to add to your project's
|
|
53
|
+
`.gitignore` by hand.
|
|
54
|
+
|
|
55
|
+
On the widget side, pair it with `HttpStorageAdapter` and `submitFlow`:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { WebRemarq, HttpStorageAdapter } from 'web-remarq'
|
|
59
|
+
WebRemarq.init({ submitFlow: true, storage: new HttpStorageAdapter() })
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Watching for new feedback
|
|
63
|
+
|
|
64
|
+
`watch_annotations` returns immediately if pending annotations already exist;
|
|
65
|
+
otherwise it blocks (long-poll) until one appears or `timeoutSeconds` elapses
|
|
66
|
+
(default 25, max 120), then returns `{ annotations: [], total: 0, timedOut: true }`.
|
|
67
|
+
Drafts are never delivered by `watch_annotations` - only annotations a designer
|
|
68
|
+
has submitted. `list_annotations` / `get_annotation` can still return drafts
|
|
69
|
+
when queried directly.
|
|
70
|
+
Typical agent loop:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
loop:
|
|
74
|
+
result = watch_annotations({ timeoutSeconds: 25 })
|
|
75
|
+
if result.timedOut: continue
|
|
76
|
+
for each annotation in result.annotations:
|
|
77
|
+
acknowledge({ id: annotation.id }) # stop it from being redelivered
|
|
78
|
+
... work the fix ...
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Parallel mode (dispatcher + background subagents)
|
|
82
|
+
|
|
83
|
+
The serial loop above blocks on each fix: feedback that arrives while the
|
|
84
|
+
agent is editing waits its turn. If the client can run background subagents
|
|
85
|
+
(Claude Code's Task tool, for example), run the main agent as a dispatcher
|
|
86
|
+
instead - acknowledge, hand off, return to watching:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
loop:
|
|
90
|
+
result = watch_annotations({ timeoutSeconds: 60 })
|
|
91
|
+
if result.timedOut: continue
|
|
92
|
+
for each annotation in result.annotations:
|
|
93
|
+
acknowledge({ id: annotation.id }) # BEFORE dispatch - closes the
|
|
94
|
+
# redelivery window while the
|
|
95
|
+
# subagent spins up
|
|
96
|
+
dispatch background subagent: # fix the files, then claim_fix
|
|
97
|
+
# do not wait for subagents - go straight back to watch_annotations
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Copy-paste prompt to put an agent on duty in this mode:
|
|
101
|
+
|
|
102
|
+
> You are on annotation duty for this project. Designers drop feedback via
|
|
103
|
+
> the web-remarq MCP server. Run this loop until I tell you to stop: call
|
|
104
|
+
> `watch_annotations` (timeoutSeconds: 60); if it times out, call it again.
|
|
105
|
+
> For EACH annotation it returns, call `acknowledge` with its id first, then
|
|
106
|
+
> dispatch a background subagent that applies the fix to the project files
|
|
107
|
+
> and calls `claim_fix` when done. Do not fix anything yourself in the main
|
|
108
|
+
> loop and do not wait for subagents - go straight back to
|
|
109
|
+
> `watch_annotations` so new feedback is never missed. If a comment is
|
|
110
|
+
> ambiguous or unactionable, `dismiss` it with a reason instead of guessing.
|
|
111
|
+
|
|
112
|
+
## Cloud mode prerequisites
|
|
21
113
|
|
|
22
114
|
1. A Supabase project provisioned with `@web-remarq/cloud` (≥0.2.0). Run both
|
|
23
115
|
`001_init.sql` and `002_lifecycle.sql` from the cloud package.
|
|
@@ -45,24 +137,31 @@ consult their MCP setup docs. The JSON shape is the same across editors:
|
|
|
45
137
|
}
|
|
46
138
|
```
|
|
47
139
|
|
|
48
|
-
|
|
49
|
-
|
|
140
|
+
Setting any one of `REMARQ_PROJECT_KEY` / `REMARQ_SUPABASE_URL` /
|
|
141
|
+
`REMARQ_SUPABASE_ANON_KEY` switches the server to cloud mode, and then all
|
|
142
|
+
three are required - the server exits with code 1 and a clear stderr message
|
|
143
|
+
if any are missing or malformed. Leave all three unset for local mode.
|
|
50
144
|
|
|
51
145
|
## Tools
|
|
52
146
|
|
|
53
147
|
| Tool | Input | Returns |
|
|
54
148
|
|------|-------|---------|
|
|
55
|
-
| `list_annotations` | `{ route?, status?, viewportBucket?, file?, limit? }` | `{ annotations[], total }` |
|
|
56
|
-
| `get_annotation` | `{ id }` | Full `AgentAnnotation` shape (source + searchHints + lifecycle) |
|
|
149
|
+
| `list_annotations` | `{ route?, status?, viewportBucket?, file?, limit? }` | `{ annotations[], total }` - `status` accepts `draft`, `pending`, `in_progress`, `fixed_unverified`, `verified`, `dismissed`; each item carries `quality` (`clear` \| `ambiguous` \| `unactionable`) when an AI pre-flight check ran |
|
|
150
|
+
| `get_annotation` | `{ id }` | Full `AgentAnnotation` shape (source + searchHints + lifecycle + `qualityCheck` when present) |
|
|
57
151
|
| `acknowledge` | `{ id }` | `{ ok, status }` after `pending → in_progress` |
|
|
58
152
|
| `claim_fix` | `{ id }` | `{ ok, status }` after `pending\|in_progress → fixed_unverified` |
|
|
59
153
|
| `dismiss` | `{ id, reason? }` | `{ ok, status }` after non-terminal → `dismissed` |
|
|
154
|
+
| `watch_annotations` | `{ timeoutSeconds? }` (1-120, default 25) | `{ annotations[], total, timedOut }` - long-polls for new pending annotations |
|
|
155
|
+
|
|
156
|
+
When `qualityCheck.score` is `ambiguous` or `unactionable`, the comment likely needs designer clarification — prefer `dismiss` with a reason over guessing at intent.
|
|
60
157
|
|
|
61
158
|
### Error codes
|
|
62
159
|
|
|
63
160
|
- `annotation_not_found` — id absent in project (also returned if RLS hides it)
|
|
64
161
|
- `invalid_transition` — lifecycle action not allowed from current status; payload includes `currentStatus`
|
|
65
|
-
- `storage_error`
|
|
162
|
+
- `storage_error` - Supabase / network failure in cloud mode, or a local
|
|
163
|
+
file-store error (e.g. corrupted store) in local mode; payload includes root
|
|
164
|
+
cause
|
|
66
165
|
- `validation_error` — input failed zod schema (auto from MCP SDK)
|
|
67
166
|
|
|
68
167
|
## License
|
package/dist/server.js
CHANGED
|
@@ -16,17 +16,30 @@ function require_(env, key) {
|
|
|
16
16
|
}
|
|
17
17
|
return v;
|
|
18
18
|
}
|
|
19
|
+
var DEFAULT_PORT = 1817;
|
|
20
|
+
var DEFAULT_DATA_FILE = ".remarq/annotations.json";
|
|
19
21
|
function parseEnv(env) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (
|
|
24
|
-
|
|
22
|
+
const cloudRequested = Boolean(
|
|
23
|
+
env.REMARQ_PROJECT_KEY || env.REMARQ_SUPABASE_URL || env.REMARQ_SUPABASE_ANON_KEY
|
|
24
|
+
);
|
|
25
|
+
if (cloudRequested) {
|
|
26
|
+
const projectKey = require_(env, "REMARQ_PROJECT_KEY");
|
|
27
|
+
const supabaseUrl = require_(env, "REMARQ_SUPABASE_URL");
|
|
28
|
+
const supabaseAnonKey = require_(env, "REMARQ_SUPABASE_ANON_KEY");
|
|
29
|
+
if (!projectKey.startsWith("pk_")) {
|
|
30
|
+
throw new ConfigError("REMARQ_PROJECT_KEY must start with `pk_` (generated by `npx @web-remarq/cloud gen-key`)");
|
|
31
|
+
}
|
|
32
|
+
if (!supabaseUrl.startsWith("https://")) {
|
|
33
|
+
throw new ConfigError("REMARQ_SUPABASE_URL must start with https://");
|
|
34
|
+
}
|
|
35
|
+
return { mode: "cloud", projectKey, supabaseUrl, supabaseAnonKey };
|
|
25
36
|
}
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
const rawPort = env.REMARQ_PORT ?? String(DEFAULT_PORT);
|
|
38
|
+
const port = Number(rawPort);
|
|
39
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
40
|
+
throw new ConfigError(`REMARQ_PORT must be an integer between 1 and 65535, got "${rawPort}"`);
|
|
28
41
|
}
|
|
29
|
-
return {
|
|
42
|
+
return { mode: "local", port, dataFile: env.REMARQ_DATA_FILE ?? DEFAULT_DATA_FILE };
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
// src/storage-factory.ts
|
|
@@ -40,6 +53,177 @@ function createStorage(config) {
|
|
|
40
53
|
});
|
|
41
54
|
}
|
|
42
55
|
|
|
56
|
+
// src/file-storage-adapter.ts
|
|
57
|
+
import { EventEmitter } from "events";
|
|
58
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
59
|
+
import { basename, dirname, join } from "path";
|
|
60
|
+
var FileStorageAdapter = class {
|
|
61
|
+
constructor(filePath) {
|
|
62
|
+
this.filePath = filePath;
|
|
63
|
+
/** Monotonic revision — bumps on every mutation. Exposed via GET /store. */
|
|
64
|
+
this.rev = 0;
|
|
65
|
+
this.emitter = new EventEmitter();
|
|
66
|
+
/** Serializes mutations (save/remove/clear) so concurrent read-modify-write ops don't clobber each other. */
|
|
67
|
+
this.queue = Promise.resolve();
|
|
68
|
+
this.emitter.setMaxListeners(0);
|
|
69
|
+
}
|
|
70
|
+
async load() {
|
|
71
|
+
if (!existsSync(this.filePath)) return null;
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(readFileSync(this.filePath, "utf8"));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
77
|
+
throw new Error(`annotations store corrupted at ${this.filePath}: ${message}`);
|
|
78
|
+
}
|
|
79
|
+
const store = parsed;
|
|
80
|
+
return {
|
|
81
|
+
version: 1,
|
|
82
|
+
annotations: Array.isArray(store.annotations) ? store.annotations : []
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async save(annotation) {
|
|
86
|
+
return this.enqueue(async () => {
|
|
87
|
+
const store = await this.load() ?? { version: 1, annotations: [] };
|
|
88
|
+
const idx = store.annotations.findIndex((a) => a.id === annotation.id);
|
|
89
|
+
if (idx === -1) store.annotations.push(annotation);
|
|
90
|
+
else store.annotations[idx] = annotation;
|
|
91
|
+
this.persist(store);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
async remove(id) {
|
|
95
|
+
return this.enqueue(async () => {
|
|
96
|
+
const store = await this.load() ?? { version: 1, annotations: [] };
|
|
97
|
+
store.annotations = store.annotations.filter((a) => a.id !== id);
|
|
98
|
+
this.persist(store);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async clear() {
|
|
102
|
+
return this.enqueue(async () => {
|
|
103
|
+
this.persist({ version: 1, annotations: [] });
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
/** Chains `op` onto the mutation queue; a rejected op doesn't poison later ops. */
|
|
107
|
+
enqueue(op) {
|
|
108
|
+
const result = this.queue.then(op);
|
|
109
|
+
this.queue = result.catch(() => void 0);
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
/** Resolves true on the next mutation, false after timeoutMs. */
|
|
113
|
+
waitForChange(timeoutMs) {
|
|
114
|
+
return new Promise((resolve) => {
|
|
115
|
+
const onChange = () => {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
resolve(true);
|
|
118
|
+
};
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
this.emitter.off("change", onChange);
|
|
121
|
+
resolve(false);
|
|
122
|
+
}, timeoutMs);
|
|
123
|
+
this.emitter.once("change", onChange);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
persist(store) {
|
|
127
|
+
const dir = dirname(this.filePath);
|
|
128
|
+
mkdirSync(dir, { recursive: true });
|
|
129
|
+
if (basename(dir) === ".remarq") {
|
|
130
|
+
const gitignore = join(dir, ".gitignore");
|
|
131
|
+
if (!existsSync(gitignore)) writeFileSync(gitignore, "*\n");
|
|
132
|
+
}
|
|
133
|
+
const tmp = `${this.filePath}.tmp`;
|
|
134
|
+
writeFileSync(tmp, JSON.stringify(store, null, 2));
|
|
135
|
+
renameSync(tmp, this.filePath);
|
|
136
|
+
this.rev++;
|
|
137
|
+
this.emitter.emit("change");
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// src/http-server.ts
|
|
142
|
+
import * as http from "http";
|
|
143
|
+
var CORS_HEADERS = {
|
|
144
|
+
"Access-Control-Allow-Origin": "*",
|
|
145
|
+
"Access-Control-Allow-Methods": "GET, PUT, DELETE, OPTIONS",
|
|
146
|
+
"Access-Control-Allow-Headers": "content-type"
|
|
147
|
+
};
|
|
148
|
+
var MAX_BODY_BYTES = 1024 * 1024;
|
|
149
|
+
function json(res, status, body) {
|
|
150
|
+
res.writeHead(status, { ...CORS_HEADERS, "content-type": "application/json" });
|
|
151
|
+
res.end(JSON.stringify(body));
|
|
152
|
+
}
|
|
153
|
+
function readBody(req) {
|
|
154
|
+
return new Promise((resolve, reject) => {
|
|
155
|
+
let size = 0;
|
|
156
|
+
const chunks = [];
|
|
157
|
+
req.on("data", (chunk) => {
|
|
158
|
+
size += chunk.length;
|
|
159
|
+
if (size > MAX_BODY_BYTES) {
|
|
160
|
+
reject(new Error("body too large"));
|
|
161
|
+
req.destroy();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
chunks.push(chunk);
|
|
165
|
+
});
|
|
166
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
167
|
+
req.on("error", reject);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
async function handle(req, res, storage) {
|
|
171
|
+
if (req.method === "OPTIONS") {
|
|
172
|
+
res.writeHead(204, CORS_HEADERS);
|
|
173
|
+
res.end();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
|
177
|
+
const annMatch = pathname.match(/^\/annotations\/([^/]+)$/);
|
|
178
|
+
try {
|
|
179
|
+
if (req.method === "GET" && pathname === "/store") {
|
|
180
|
+
const rev = storage.rev;
|
|
181
|
+
const store = await storage.load() ?? { version: 1, annotations: [] };
|
|
182
|
+
json(res, 200, { rev, store });
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (req.method === "PUT" && annMatch) {
|
|
186
|
+
const id = decodeURIComponent(annMatch[1]);
|
|
187
|
+
let annotation;
|
|
188
|
+
try {
|
|
189
|
+
annotation = JSON.parse(await readBody(req));
|
|
190
|
+
} catch {
|
|
191
|
+
json(res, 400, { error: "invalid JSON body" });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!annotation || annotation.id !== id) {
|
|
195
|
+
json(res, 400, { error: "annotation.id must match the URL id" });
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
await storage.save(annotation);
|
|
199
|
+
json(res, 200, { rev: storage.rev });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (req.method === "DELETE" && annMatch) {
|
|
203
|
+
await storage.remove(decodeURIComponent(annMatch[1]));
|
|
204
|
+
json(res, 200, { rev: storage.rev });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (req.method === "DELETE" && pathname === "/annotations") {
|
|
208
|
+
await storage.clear();
|
|
209
|
+
json(res, 200, { rev: storage.rev });
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
json(res, 404, { error: "not found" });
|
|
213
|
+
} catch (err) {
|
|
214
|
+
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function startHttpServer(storage, port) {
|
|
218
|
+
const server = http.createServer((req, res) => {
|
|
219
|
+
void handle(req, res, storage);
|
|
220
|
+
});
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
server.once("error", reject);
|
|
223
|
+
server.listen(port, "127.0.0.1", () => resolve(server));
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
43
227
|
// src/tools/list-annotations.ts
|
|
44
228
|
import { z } from "zod";
|
|
45
229
|
|
|
@@ -58,7 +242,7 @@ function toolSuccess(value) {
|
|
|
58
242
|
}
|
|
59
243
|
|
|
60
244
|
// src/tools/list-annotations.ts
|
|
61
|
-
var STATUS_VALUES = ["pending", "in_progress", "fixed_unverified", "verified", "dismissed"];
|
|
245
|
+
var STATUS_VALUES = ["draft", "pending", "in_progress", "fixed_unverified", "verified", "dismissed"];
|
|
62
246
|
var listAnnotationsInputSchema = z.object({
|
|
63
247
|
route: z.string().optional(),
|
|
64
248
|
status: z.union([z.enum(STATUS_VALUES), z.array(z.enum(STATUS_VALUES))]).optional(),
|
|
@@ -87,7 +271,8 @@ function toThin(a) {
|
|
|
87
271
|
status: a.status,
|
|
88
272
|
viewport: a.viewportBucket,
|
|
89
273
|
timestamp: a.timestamp,
|
|
90
|
-
source: parseSource(a)
|
|
274
|
+
source: parseSource(a),
|
|
275
|
+
...a.qualityCheck ? { quality: a.qualityCheck.score } : {}
|
|
91
276
|
};
|
|
92
277
|
}
|
|
93
278
|
async function handleListAnnotations(input, storage) {
|
|
@@ -263,15 +448,43 @@ async function handleDismiss(input, storage) {
|
|
|
263
448
|
return toolSuccess({ ok: true, status: result.status });
|
|
264
449
|
}
|
|
265
450
|
|
|
451
|
+
// src/tools/watch-annotations.ts
|
|
452
|
+
import { z as z6 } from "zod";
|
|
453
|
+
var DEFAULT_TIMEOUT_SECONDS = 25;
|
|
454
|
+
var watchAnnotationsInputSchema = z6.object({
|
|
455
|
+
timeoutSeconds: z6.number().int().min(1).max(120).optional()
|
|
456
|
+
});
|
|
457
|
+
async function handleWatchAnnotations(input, storage, waitForChange) {
|
|
458
|
+
const timeoutMs = (input.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1e3;
|
|
459
|
+
const deadline = Date.now() + timeoutMs;
|
|
460
|
+
for (; ; ) {
|
|
461
|
+
let store;
|
|
462
|
+
try {
|
|
463
|
+
store = await storage.load();
|
|
464
|
+
} catch (err) {
|
|
465
|
+
return toolError("storage_error", err instanceof Error ? err.message : String(err));
|
|
466
|
+
}
|
|
467
|
+
const pending = (store?.annotations ?? []).filter((a) => a.status === "pending");
|
|
468
|
+
if (pending.length > 0) {
|
|
469
|
+
return toolSuccess({ annotations: pending.map(toThin), total: pending.length, timedOut: false });
|
|
470
|
+
}
|
|
471
|
+
const remaining = deadline - Date.now();
|
|
472
|
+
if (remaining <= 0) {
|
|
473
|
+
return toolSuccess({ annotations: [], total: 0, timedOut: true });
|
|
474
|
+
}
|
|
475
|
+
await waitForChange(remaining);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
266
479
|
// src/tools/index.ts
|
|
267
480
|
function cast(p) {
|
|
268
481
|
return p;
|
|
269
482
|
}
|
|
270
|
-
function registerTools(server, storage) {
|
|
483
|
+
function registerTools(server, storage, opts) {
|
|
271
484
|
server.registerTool(
|
|
272
485
|
"list_annotations",
|
|
273
486
|
{
|
|
274
|
-
description: "List annotations in the project with optional filters (route, status, viewport, file).",
|
|
487
|
+
description: "List annotations in the project with optional filters (route, status, viewport, file). Each item carries a `quality` score (clear | ambiguous | unactionable) when an AI pre-flight check ran.",
|
|
275
488
|
inputSchema: listAnnotationsInputSchema.shape
|
|
276
489
|
},
|
|
277
490
|
(input) => cast(handleListAnnotations(input, storage))
|
|
@@ -279,7 +492,7 @@ function registerTools(server, storage) {
|
|
|
279
492
|
server.registerTool(
|
|
280
493
|
"get_annotation",
|
|
281
494
|
{
|
|
282
|
-
description:
|
|
495
|
+
description: 'Get full annotation details including source file:line:col, grep search hints, and the AI quality verdict (`qualityCheck`). If qualityCheck.score is "ambiguous" or "unactionable", the comment may need designer clarification before fixing \u2014 consider dismissing with a reason instead of guessing.',
|
|
283
496
|
inputSchema: getAnnotationInputSchema.shape
|
|
284
497
|
},
|
|
285
498
|
(input) => cast(handleGetAnnotation(input, storage))
|
|
@@ -308,9 +521,18 @@ function registerTools(server, storage) {
|
|
|
308
521
|
},
|
|
309
522
|
(input) => cast(handleDismiss(input, storage))
|
|
310
523
|
);
|
|
524
|
+
server.registerTool(
|
|
525
|
+
"watch_annotations",
|
|
526
|
+
{
|
|
527
|
+
description: 'Wait for pending annotations (long-poll). Returns immediately if pending annotations exist; otherwise blocks until one appears or timeoutSeconds (default 25) elapses, then returns {"annotations": [], "timedOut": true}. Call this in a loop to continuously pick up designer feedback. If your environment can run background subagents (e.g. a Task tool), act as a dispatcher: for each returned annotation call acknowledge first (so it stops being redelivered), hand the fix to a background subagent that will claim_fix when done, and return to watch_annotations immediately instead of fixing inline - new feedback must never wait on a fix in progress. Without subagents, acknowledge each annotation before you start fixing it.',
|
|
528
|
+
inputSchema: watchAnnotationsInputSchema.shape
|
|
529
|
+
},
|
|
530
|
+
(input) => cast(handleWatchAnnotations(input, storage, opts.waitForChange))
|
|
531
|
+
);
|
|
311
532
|
}
|
|
312
533
|
|
|
313
534
|
// src/server.ts
|
|
535
|
+
var CLOUD_POLL_MS = 3e3;
|
|
314
536
|
async function main() {
|
|
315
537
|
let config;
|
|
316
538
|
try {
|
|
@@ -322,12 +544,25 @@ async function main() {
|
|
|
322
544
|
}
|
|
323
545
|
throw err;
|
|
324
546
|
}
|
|
325
|
-
|
|
547
|
+
let storage;
|
|
548
|
+
let waitForChange;
|
|
549
|
+
if (config.mode === "local") {
|
|
550
|
+
const adapter = new FileStorageAdapter(config.dataFile);
|
|
551
|
+
await startHttpServer(adapter, config.port);
|
|
552
|
+
console.error(
|
|
553
|
+
`[web-remarq-mcp] local mode \u2014 widget endpoint http://127.0.0.1:${config.port}, store ${config.dataFile}`
|
|
554
|
+
);
|
|
555
|
+
storage = adapter;
|
|
556
|
+
waitForChange = (ms) => adapter.waitForChange(ms);
|
|
557
|
+
} else {
|
|
558
|
+
storage = createStorage(config);
|
|
559
|
+
waitForChange = (ms) => new Promise((resolve) => setTimeout(() => resolve(false), Math.min(ms, CLOUD_POLL_MS)));
|
|
560
|
+
}
|
|
326
561
|
const server = new McpServer({
|
|
327
562
|
name: "web-remarq",
|
|
328
|
-
version: "0.
|
|
563
|
+
version: "0.2.0"
|
|
329
564
|
});
|
|
330
|
-
registerTools(server, storage);
|
|
565
|
+
registerTools(server, storage, { waitForChange });
|
|
331
566
|
const transport = new StdioServerTransport();
|
|
332
567
|
await server.connect(transport);
|
|
333
568
|
}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/config.ts","../src/storage-factory.ts","../src/tools/list-annotations.ts","../src/errors.ts","../src/tools/get-annotation.ts","../src/tools/acknowledge.ts","../src/tools/claim-fix.ts","../src/tools/dismiss.ts","../src/tools/index.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { parseEnv, ConfigError } from './config.js'\nimport { createStorage } from './storage-factory.js'\nimport { registerTools } from './tools/index.js'\n\nasync function main(): Promise<void> {\n let config\n try {\n config = parseEnv(process.env)\n } catch (err) {\n if (err instanceof ConfigError) {\n console.error(`[web-remarq-mcp] config error: ${err.message}`)\n process.exit(1)\n }\n throw err\n }\n\n const storage = createStorage(config)\n const server = new McpServer({\n name: 'web-remarq',\n version: '0.1.0',\n })\n\n registerTools(server, storage)\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => {\n console.error('[web-remarq-mcp] fatal:', err)\n process.exit(1)\n})\n","export interface MCPConfig {\n projectKey: string\n supabaseUrl: string\n supabaseAnonKey: string\n}\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigError'\n }\n}\n\nfunction require_(env: NodeJS.ProcessEnv, key: string): string {\n const v = env[key]\n if (!v || v.trim() === '') {\n throw new ConfigError(`Missing required env var: ${key}`)\n }\n return v\n}\n\nexport function parseEnv(env: NodeJS.ProcessEnv): MCPConfig {\n const projectKey = require_(env, 'REMARQ_PROJECT_KEY')\n const supabaseUrl = require_(env, 'REMARQ_SUPABASE_URL')\n const supabaseAnonKey = require_(env, 'REMARQ_SUPABASE_ANON_KEY')\n\n if (!projectKey.startsWith('pk_')) {\n throw new ConfigError('REMARQ_PROJECT_KEY must start with `pk_` (generated by `npx @web-remarq/cloud gen-key`)')\n }\n if (!supabaseUrl.startsWith('https://')) {\n throw new ConfigError('REMARQ_SUPABASE_URL must start with https://')\n }\n\n return { projectKey, supabaseUrl, supabaseAnonKey }\n}\n","import { createCloudStorage } from '@web-remarq/cloud'\nimport type { StorageAdapter } from 'web-remarq'\nimport type { MCPConfig } from './config'\n\nexport function createStorage(config: MCPConfig): StorageAdapter {\n return createCloudStorage({\n projectKey: config.projectKey,\n supabaseUrl: config.supabaseUrl,\n supabaseAnonKey: config.supabaseAnonKey,\n onError: 'throw',\n })\n}\n","import { z } from 'zod'\nimport type { Annotation, AnnotationStatus, StorageAdapter } from 'web-remarq'\nimport { toolError, toolSuccess } from '../errors'\n\nconst STATUS_VALUES = ['pending', 'in_progress', 'fixed_unverified', 'verified', 'dismissed'] as const\n\nexport const listAnnotationsInputSchema = z.object({\n route: z.string().optional(),\n status: z.union([z.enum(STATUS_VALUES), z.array(z.enum(STATUS_VALUES))]).optional(),\n viewportBucket: z.number().int().optional(),\n file: z.string().optional(),\n limit: z.number().int().min(1).max(200).optional(),\n})\n\nexport type ListAnnotationsInput = z.infer<typeof listAnnotationsInputSchema>\n\ninterface ThinAnnotation {\n id: string\n route: string\n comment: string\n status: AnnotationStatus\n viewport: number\n timestamp: number\n source: { file: string; line: number; column: number; component?: string } | null\n}\n\nfunction parseSource(annotation: Annotation): ThinAnnotation['source'] {\n const fp = annotation.fingerprint\n const raw = fp.sourceLocation ?? fp.detectedSource\n if (!raw) return null\n const parts = raw.split(':')\n if (parts.length < 2) return null\n const column = parseInt(parts.pop()!, 10)\n const line = parseInt(parts.pop()!, 10)\n const file = parts.join(':')\n if (!file || isNaN(line)) return null\n const component = fp.componentName ?? fp.detectedComponent ?? undefined\n return { file, line, column: isNaN(column) ? 0 : column, ...(component ? { component } : {}) }\n}\n\nfunction toThin(a: Annotation): ThinAnnotation {\n return {\n id: a.id,\n route: a.route,\n comment: a.comment,\n status: a.status,\n viewport: a.viewportBucket,\n timestamp: a.timestamp,\n source: parseSource(a),\n }\n}\n\nexport async function handleListAnnotations(input: ListAnnotationsInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const all = store?.annotations ?? []\n\n const statusFilter = Array.isArray(input.status)\n ? new Set(input.status)\n : input.status\n ? new Set([input.status])\n : null\n\n const filtered = all.filter((a) => {\n if (input.route !== undefined && a.route !== input.route) return false\n if (statusFilter && !statusFilter.has(a.status)) return false\n if (input.viewportBucket !== undefined && a.viewportBucket !== input.viewportBucket) return false\n if (input.file !== undefined) {\n const src = a.fingerprint.sourceLocation ?? a.fingerprint.detectedSource ?? ''\n if (!src.includes(input.file)) return false\n }\n return true\n })\n\n const limit = input.limit ?? 50\n const thinned = filtered.slice(0, limit).map(toThin)\n\n return toolSuccess({ annotations: thinned, total: filtered.length })\n}\n","export type ToolErrorCode =\n | 'annotation_not_found'\n | 'invalid_transition'\n | 'storage_error'\n | 'validation_error'\n\nexport interface ToolErrorPayload {\n code: ToolErrorCode\n message: string\n details?: Record<string, unknown>\n}\n\nexport interface ToolErrorResult {\n isError: true\n content: Array<{ type: 'text'; text: string }>\n}\n\nexport function toolError(code: ToolErrorCode, message: string, details?: Record<string, unknown>): ToolErrorResult {\n const payload: ToolErrorPayload = { code, message, details }\n return {\n isError: true,\n content: [{ type: 'text', text: JSON.stringify(payload) }],\n }\n}\n\nexport interface ToolSuccessResult {\n isError?: false\n content: Array<{ type: 'text'; text: string }>\n}\n\nexport function toolSuccess(value: unknown): ToolSuccessResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(value) }],\n }\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { generateAgentExport } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const getAnnotationInputSchema = z.object({\n id: z.string(),\n})\n\nexport type GetAnnotationInput = z.infer<typeof getAnnotationInputSchema>\n\nexport async function handleGetAnnotation(input: GetAnnotationInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n // Reuse core agent-export to build the rich shape (source + searchHints + lifecycle).\n // generateAgentExport takes an array — pass the single annotation, then extract.\n const agentExport = generateAgentExport([annotation], annotation.viewportBucket)\n const agentAnnotation = agentExport.annotations[0]\n\n return toolSuccess(agentAnnotation)\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { transition, InvalidTransitionError } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const acknowledgeInputSchema = z.object({\n id: z.string(),\n})\n\nexport type AcknowledgeInput = z.infer<typeof acknowledgeInputSchema>\n\nexport async function handleAcknowledge(input: AcknowledgeInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n let result\n try {\n result = transition(annotation, 'acknowledge', { actor: 'agent' })\n } catch (err) {\n if (err instanceof InvalidTransitionError) {\n return toolError('invalid_transition', err.message, {\n currentStatus: annotation.status,\n requestedTransition: 'acknowledge',\n })\n }\n throw err\n }\n\n const updated = {\n ...annotation,\n status: result.status,\n lifecycle: [...annotation.lifecycle, result.event],\n }\n\n try {\n await storage.save(updated)\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n return toolSuccess({ ok: true, status: result.status })\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { transition, InvalidTransitionError } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const claimFixInputSchema = z.object({\n id: z.string(),\n})\n\nexport type ClaimFixInput = z.infer<typeof claimFixInputSchema>\n\nexport async function handleClaimFix(input: ClaimFixInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n let result\n try {\n result = transition(annotation, 'claimFix', { actor: 'agent' })\n } catch (err) {\n if (err instanceof InvalidTransitionError) {\n return toolError('invalid_transition', err.message, {\n currentStatus: annotation.status,\n requestedTransition: 'claimFix',\n })\n }\n throw err\n }\n\n const updated = {\n ...annotation,\n status: result.status,\n lifecycle: [...annotation.lifecycle, result.event],\n }\n\n try {\n await storage.save(updated)\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n return toolSuccess({ ok: true, status: result.status })\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { transition, InvalidTransitionError } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const dismissInputSchema = z.object({\n id: z.string(),\n reason: z.string().optional(),\n})\n\nexport type DismissInput = z.infer<typeof dismissInputSchema>\n\nexport async function handleDismiss(input: DismissInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n let result\n try {\n result = transition(annotation, 'dismiss', { actor: 'agent', reason: input.reason })\n } catch (err) {\n if (err instanceof InvalidTransitionError) {\n return toolError('invalid_transition', err.message, {\n currentStatus: annotation.status,\n requestedTransition: 'dismiss',\n })\n }\n throw err\n }\n\n const updated = {\n ...annotation,\n status: result.status,\n lifecycle: [...annotation.lifecycle, result.event],\n }\n\n try {\n await storage.save(updated)\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n return toolSuccess({ ok: true, status: result.status })\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type { StorageAdapter } from 'web-remarq'\n\nimport { listAnnotationsInputSchema, handleListAnnotations } from './list-annotations.js'\nimport { getAnnotationInputSchema, handleGetAnnotation } from './get-annotation.js'\nimport { acknowledgeInputSchema, handleAcknowledge } from './acknowledge.js'\nimport { claimFixInputSchema, handleClaimFix } from './claim-fix.js'\nimport { dismissInputSchema, handleDismiss } from './dismiss.js'\n\n// Cast helper: our tool results satisfy CallToolResult at runtime; the SDK's\n// inferred type carries an index signature that our narrower types lack.\nfunction cast(p: Promise<{ isError?: boolean; content: Array<{ type: 'text'; text: string }> }>): Promise<CallToolResult> {\n return p as Promise<CallToolResult>\n}\n\nexport function registerTools(server: McpServer, storage: StorageAdapter): void {\n server.registerTool(\n 'list_annotations',\n {\n description: 'List annotations in the project with optional filters (route, status, viewport, file).',\n inputSchema: listAnnotationsInputSchema.shape,\n },\n (input) => cast(handleListAnnotations(input, storage)),\n )\n\n server.registerTool(\n 'get_annotation',\n {\n description: 'Get full annotation details including source file:line:col and grep search hints.',\n inputSchema: getAnnotationInputSchema.shape,\n },\n (input) => cast(handleGetAnnotation(input, storage)),\n )\n\n server.registerTool(\n 'acknowledge',\n {\n description: 'Mark an annotation as in-progress (pending → in_progress).',\n inputSchema: acknowledgeInputSchema.shape,\n },\n (input) => cast(handleAcknowledge(input, storage)),\n )\n\n server.registerTool(\n 'claim_fix',\n {\n description: 'Claim a fix for an annotation (→ fixed_unverified). Human verification still required.',\n inputSchema: claimFixInputSchema.shape,\n },\n (input) => cast(handleClaimFix(input, storage)),\n )\n\n server.registerTool(\n 'dismiss',\n {\n description: 'Dismiss an annotation with an optional reason (terminal state).',\n inputSchema: dismissInputSchema.shape,\n },\n (input) => cast(handleDismiss(input, storage)),\n )\n}\n"],"mappings":"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","names":["z","z","z","z","z","transition","InvalidTransitionError","z","transition","InvalidTransitionError","z","transition","InvalidTransitionError","z","transition","InvalidTransitionError"]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/config.ts","../src/storage-factory.ts","../src/file-storage-adapter.ts","../src/http-server.ts","../src/tools/list-annotations.ts","../src/errors.ts","../src/tools/get-annotation.ts","../src/tools/acknowledge.ts","../src/tools/claim-fix.ts","../src/tools/dismiss.ts","../src/tools/watch-annotations.ts","../src/tools/index.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport type { StorageAdapter } from 'web-remarq'\nimport { parseEnv, ConfigError } from './config.js'\nimport { createStorage } from './storage-factory.js'\nimport { FileStorageAdapter } from './file-storage-adapter.js'\nimport { startHttpServer } from './http-server.js'\nimport { registerTools, type WaitForChange } from './tools/index.js'\n\nconst CLOUD_POLL_MS = 3000\n\nasync function main(): Promise<void> {\n let config\n try {\n config = parseEnv(process.env)\n } catch (err) {\n if (err instanceof ConfigError) {\n console.error(`[web-remarq-mcp] config error: ${err.message}`)\n process.exit(1)\n }\n throw err\n }\n\n let storage: StorageAdapter\n let waitForChange: WaitForChange\n\n if (config.mode === 'local') {\n const adapter = new FileStorageAdapter(config.dataFile)\n await startHttpServer(adapter, config.port)\n console.error(\n `[web-remarq-mcp] local mode — widget endpoint http://127.0.0.1:${config.port}, store ${config.dataFile}`,\n )\n storage = adapter\n waitForChange = (ms) => adapter.waitForChange(ms)\n } else {\n storage = createStorage(config)\n waitForChange = (ms) =>\n new Promise((resolve) => setTimeout(() => resolve(false), Math.min(ms, CLOUD_POLL_MS)))\n }\n\n const server = new McpServer({\n name: 'web-remarq',\n version: '0.2.0',\n })\n\n registerTools(server, storage, { waitForChange })\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => {\n console.error('[web-remarq-mcp] fatal:', err)\n process.exit(1)\n})\n","export interface CloudConfig {\n mode: 'cloud'\n projectKey: string\n supabaseUrl: string\n supabaseAnonKey: string\n}\n\nexport interface LocalConfig {\n mode: 'local'\n port: number\n dataFile: string\n}\n\nexport type MCPConfig = CloudConfig | LocalConfig\n\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ConfigError'\n }\n}\n\nfunction require_(env: NodeJS.ProcessEnv, key: string): string {\n const v = env[key]\n if (!v || v.trim() === '') {\n throw new ConfigError(`Missing required env var: ${key}`)\n }\n return v\n}\n\nconst DEFAULT_PORT = 1817\nconst DEFAULT_DATA_FILE = '.remarq/annotations.json'\n\nexport function parseEnv(env: NodeJS.ProcessEnv): MCPConfig {\n const cloudRequested = Boolean(\n env.REMARQ_PROJECT_KEY || env.REMARQ_SUPABASE_URL || env.REMARQ_SUPABASE_ANON_KEY,\n )\n\n if (cloudRequested) {\n const projectKey = require_(env, 'REMARQ_PROJECT_KEY')\n const supabaseUrl = require_(env, 'REMARQ_SUPABASE_URL')\n const supabaseAnonKey = require_(env, 'REMARQ_SUPABASE_ANON_KEY')\n\n if (!projectKey.startsWith('pk_')) {\n throw new ConfigError('REMARQ_PROJECT_KEY must start with `pk_` (generated by `npx @web-remarq/cloud gen-key`)')\n }\n if (!supabaseUrl.startsWith('https://')) {\n throw new ConfigError('REMARQ_SUPABASE_URL must start with https://')\n }\n\n return { mode: 'cloud', projectKey, supabaseUrl, supabaseAnonKey }\n }\n\n const rawPort = env.REMARQ_PORT ?? String(DEFAULT_PORT)\n const port = Number(rawPort)\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new ConfigError(`REMARQ_PORT must be an integer between 1 and 65535, got \"${rawPort}\"`)\n }\n\n return { mode: 'local', port, dataFile: env.REMARQ_DATA_FILE ?? DEFAULT_DATA_FILE }\n}\n","import { createCloudStorage } from '@web-remarq/cloud'\nimport type { StorageAdapter } from 'web-remarq'\nimport type { CloudConfig } from './config'\n\nexport function createStorage(config: CloudConfig): StorageAdapter {\n return createCloudStorage({\n projectKey: config.projectKey,\n supabaseUrl: config.supabaseUrl,\n supabaseAnonKey: config.supabaseAnonKey,\n onError: 'throw',\n })\n}\n","import { EventEmitter } from 'node:events'\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'\nimport { basename, dirname, join } from 'node:path'\nimport type { Annotation, AnnotationStore, StorageAdapter } from 'web-remarq'\n\n/**\n * Local-mode StorageAdapter over a JSON file (default .remarq/annotations.json).\n * All mutations flow through this process (MCP tools + the local HTTP server),\n * so change notification is a plain in-memory emitter — no fs.watch needed.\n */\nexport class FileStorageAdapter implements StorageAdapter {\n /** Monotonic revision — bumps on every mutation. Exposed via GET /store. */\n rev = 0\n private emitter = new EventEmitter()\n /** Serializes mutations (save/remove/clear) so concurrent read-modify-write ops don't clobber each other. */\n private queue: Promise<void> = Promise.resolve()\n\n constructor(private filePath: string) {\n this.emitter.setMaxListeners(0)\n }\n\n async load(): Promise<AnnotationStore | null> {\n if (!existsSync(this.filePath)) return null\n let parsed: unknown\n try {\n parsed = JSON.parse(readFileSync(this.filePath, 'utf8'))\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(`annotations store corrupted at ${this.filePath}: ${message}`)\n }\n const store = parsed as { annotations?: unknown }\n return {\n version: 1,\n annotations: Array.isArray(store.annotations) ? (store.annotations as Annotation[]) : [],\n }\n }\n\n async save(annotation: Annotation): Promise<void> {\n return this.enqueue(async () => {\n const store = (await this.load()) ?? { version: 1 as const, annotations: [] }\n const idx = store.annotations.findIndex((a) => a.id === annotation.id)\n if (idx === -1) store.annotations.push(annotation)\n else store.annotations[idx] = annotation\n this.persist(store)\n })\n }\n\n async remove(id: string): Promise<void> {\n return this.enqueue(async () => {\n const store = (await this.load()) ?? { version: 1 as const, annotations: [] }\n store.annotations = store.annotations.filter((a) => a.id !== id)\n this.persist(store)\n })\n }\n\n async clear(): Promise<void> {\n return this.enqueue(async () => {\n this.persist({ version: 1, annotations: [] })\n })\n }\n\n /** Chains `op` onto the mutation queue; a rejected op doesn't poison later ops. */\n private enqueue(op: () => Promise<void>): Promise<void> {\n const result = this.queue.then(op)\n // Swallow the rejection on the shared chain so the next enqueued op still runs;\n // the caller's own `result` promise still rejects with the original error.\n this.queue = result.catch(() => undefined)\n return result\n }\n\n /** Resolves true on the next mutation, false after timeoutMs. */\n waitForChange(timeoutMs: number): Promise<boolean> {\n return new Promise((resolve) => {\n const onChange = (): void => {\n clearTimeout(timer)\n resolve(true)\n }\n const timer = setTimeout(() => {\n this.emitter.off('change', onChange)\n resolve(false)\n }, timeoutMs)\n this.emitter.once('change', onChange)\n })\n }\n\n private persist(store: AnnotationStore): void {\n const dir = dirname(this.filePath)\n mkdirSync(dir, { recursive: true })\n // Auto-ignore the store when it lives in the conventional .remarq dir.\n // NEVER write a wildcard .gitignore into an arbitrary user directory.\n if (basename(dir) === '.remarq') {\n const gitignore = join(dir, '.gitignore')\n if (!existsSync(gitignore)) writeFileSync(gitignore, '*\\n')\n }\n const tmp = `${this.filePath}.tmp`\n writeFileSync(tmp, JSON.stringify(store, null, 2))\n renameSync(tmp, this.filePath)\n this.rev++\n this.emitter.emit('change')\n }\n}\n","import * as http from 'node:http'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { FileStorageAdapter } from './file-storage-adapter.js'\n\nconst CORS_HEADERS: Record<string, string> = {\n 'Access-Control-Allow-Origin': '*',\n 'Access-Control-Allow-Methods': 'GET, PUT, DELETE, OPTIONS',\n 'Access-Control-Allow-Headers': 'content-type',\n}\n\nconst MAX_BODY_BYTES = 1024 * 1024\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { ...CORS_HEADERS, 'content-type': 'application/json' })\n res.end(JSON.stringify(body))\n}\n\nfunction readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let size = 0\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > MAX_BODY_BYTES) {\n reject(new Error('body too large'))\n req.destroy()\n return\n }\n chunks.push(chunk)\n })\n req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')))\n req.on('error', reject)\n })\n}\n\nasync function handle(req: IncomingMessage, res: ServerResponse, storage: FileStorageAdapter): Promise<void> {\n if (req.method === 'OPTIONS') {\n res.writeHead(204, CORS_HEADERS)\n res.end()\n return\n }\n\n const pathname = new URL(req.url ?? '/', 'http://localhost').pathname\n const annMatch = pathname.match(/^\\/annotations\\/([^/]+)$/)\n\n try {\n if (req.method === 'GET' && pathname === '/store') {\n // Capture rev before the await, not after: a concurrent mutation between\n // load() and reading storage.rev would pair a newer rev with the older\n // content this response body carries. Under-reporting is safe (the\n // client's next poll re-diffs); over-reporting is not.\n const rev = storage.rev\n const store = (await storage.load()) ?? { version: 1 as const, annotations: [] }\n json(res, 200, { rev, store })\n return\n }\n\n if (req.method === 'PUT' && annMatch) {\n const id = decodeURIComponent(annMatch[1])\n let annotation: { id?: unknown }\n try {\n annotation = JSON.parse(await readBody(req))\n } catch {\n json(res, 400, { error: 'invalid JSON body' })\n return\n }\n if (!annotation || annotation.id !== id) {\n json(res, 400, { error: 'annotation.id must match the URL id' })\n return\n }\n // The store is trusted local input from the widget; no schema validation.\n await storage.save(annotation as never)\n json(res, 200, { rev: storage.rev })\n return\n }\n\n if (req.method === 'DELETE' && annMatch) {\n await storage.remove(decodeURIComponent(annMatch[1]))\n json(res, 200, { rev: storage.rev })\n return\n }\n\n if (req.method === 'DELETE' && pathname === '/annotations') {\n await storage.clear()\n json(res, 200, { rev: storage.rev })\n return\n }\n\n json(res, 404, { error: 'not found' })\n } catch (err) {\n json(res, 500, { error: err instanceof Error ? err.message : String(err) })\n }\n}\n\n/** Starts the widget-facing endpoint on 127.0.0.1:port. Rejects on bind errors. */\nexport function startHttpServer(storage: FileStorageAdapter, port: number): Promise<http.Server> {\n const server = http.createServer((req, res) => {\n void handle(req, res, storage)\n })\n return new Promise((resolve, reject) => {\n server.once('error', reject)\n server.listen(port, '127.0.0.1', () => resolve(server))\n })\n}\n","import { z } from 'zod'\nimport type { Annotation, AnnotationStatus, QualityCheck, StorageAdapter } from 'web-remarq'\nimport { toolError, toolSuccess } from '../errors'\n\nconst STATUS_VALUES = ['draft', 'pending', 'in_progress', 'fixed_unverified', 'verified', 'dismissed'] as const\n\nexport const listAnnotationsInputSchema = z.object({\n route: z.string().optional(),\n status: z.union([z.enum(STATUS_VALUES), z.array(z.enum(STATUS_VALUES))]).optional(),\n viewportBucket: z.number().int().optional(),\n file: z.string().optional(),\n limit: z.number().int().min(1).max(200).optional(),\n})\n\nexport type ListAnnotationsInput = z.infer<typeof listAnnotationsInputSchema>\n\nexport interface ThinAnnotation {\n id: string\n route: string\n comment: string\n status: AnnotationStatus\n viewport: number\n timestamp: number\n source: { file: string; line: number; column: number; component?: string } | null\n quality?: QualityCheck['score']\n}\n\nfunction parseSource(annotation: Annotation): ThinAnnotation['source'] {\n const fp = annotation.fingerprint\n const raw = fp.sourceLocation ?? fp.detectedSource\n if (!raw) return null\n const parts = raw.split(':')\n if (parts.length < 2) return null\n const column = parseInt(parts.pop()!, 10)\n const line = parseInt(parts.pop()!, 10)\n const file = parts.join(':')\n if (!file || isNaN(line)) return null\n const component = fp.componentName ?? fp.detectedComponent ?? undefined\n return { file, line, column: isNaN(column) ? 0 : column, ...(component ? { component } : {}) }\n}\n\nexport function toThin(a: Annotation): ThinAnnotation {\n return {\n id: a.id,\n route: a.route,\n comment: a.comment,\n status: a.status,\n viewport: a.viewportBucket,\n timestamp: a.timestamp,\n source: parseSource(a),\n ...(a.qualityCheck ? { quality: a.qualityCheck.score } : {}),\n }\n}\n\nexport async function handleListAnnotations(input: ListAnnotationsInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const all = store?.annotations ?? []\n\n const statusFilter = Array.isArray(input.status)\n ? new Set(input.status)\n : input.status\n ? new Set([input.status])\n : null\n\n const filtered = all.filter((a) => {\n if (input.route !== undefined && a.route !== input.route) return false\n if (statusFilter && !statusFilter.has(a.status)) return false\n if (input.viewportBucket !== undefined && a.viewportBucket !== input.viewportBucket) return false\n if (input.file !== undefined) {\n const src = a.fingerprint.sourceLocation ?? a.fingerprint.detectedSource ?? ''\n if (!src.includes(input.file)) return false\n }\n return true\n })\n\n const limit = input.limit ?? 50\n const thinned = filtered.slice(0, limit).map(toThin)\n\n return toolSuccess({ annotations: thinned, total: filtered.length })\n}\n","export type ToolErrorCode =\n | 'annotation_not_found'\n | 'invalid_transition'\n | 'storage_error'\n | 'validation_error'\n\nexport interface ToolErrorPayload {\n code: ToolErrorCode\n message: string\n details?: Record<string, unknown>\n}\n\nexport interface ToolErrorResult {\n isError: true\n content: Array<{ type: 'text'; text: string }>\n}\n\nexport function toolError(code: ToolErrorCode, message: string, details?: Record<string, unknown>): ToolErrorResult {\n const payload: ToolErrorPayload = { code, message, details }\n return {\n isError: true,\n content: [{ type: 'text', text: JSON.stringify(payload) }],\n }\n}\n\nexport interface ToolSuccessResult {\n isError?: false\n content: Array<{ type: 'text'; text: string }>\n}\n\nexport function toolSuccess(value: unknown): ToolSuccessResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(value) }],\n }\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { generateAgentExport } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const getAnnotationInputSchema = z.object({\n id: z.string(),\n})\n\nexport type GetAnnotationInput = z.infer<typeof getAnnotationInputSchema>\n\nexport async function handleGetAnnotation(input: GetAnnotationInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n // Reuse core agent-export to build the rich shape (source + searchHints + lifecycle).\n // generateAgentExport takes an array — pass the single annotation, then extract.\n const agentExport = generateAgentExport([annotation], annotation.viewportBucket)\n const agentAnnotation = agentExport.annotations[0]\n\n return toolSuccess(agentAnnotation)\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { transition, InvalidTransitionError } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const acknowledgeInputSchema = z.object({\n id: z.string(),\n})\n\nexport type AcknowledgeInput = z.infer<typeof acknowledgeInputSchema>\n\nexport async function handleAcknowledge(input: AcknowledgeInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n let result\n try {\n result = transition(annotation, 'acknowledge', { actor: 'agent' })\n } catch (err) {\n if (err instanceof InvalidTransitionError) {\n return toolError('invalid_transition', err.message, {\n currentStatus: annotation.status,\n requestedTransition: 'acknowledge',\n })\n }\n throw err\n }\n\n const updated = {\n ...annotation,\n status: result.status,\n lifecycle: [...annotation.lifecycle, result.event],\n }\n\n try {\n await storage.save(updated)\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n return toolSuccess({ ok: true, status: result.status })\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { transition, InvalidTransitionError } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const claimFixInputSchema = z.object({\n id: z.string(),\n})\n\nexport type ClaimFixInput = z.infer<typeof claimFixInputSchema>\n\nexport async function handleClaimFix(input: ClaimFixInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n let result\n try {\n result = transition(annotation, 'claimFix', { actor: 'agent' })\n } catch (err) {\n if (err instanceof InvalidTransitionError) {\n return toolError('invalid_transition', err.message, {\n currentStatus: annotation.status,\n requestedTransition: 'claimFix',\n })\n }\n throw err\n }\n\n const updated = {\n ...annotation,\n status: result.status,\n lifecycle: [...annotation.lifecycle, result.event],\n }\n\n try {\n await storage.save(updated)\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n return toolSuccess({ ok: true, status: result.status })\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { transition, InvalidTransitionError } from 'web-remarq/core'\nimport { toolError, toolSuccess } from '../errors'\n\nexport const dismissInputSchema = z.object({\n id: z.string(),\n reason: z.string().optional(),\n})\n\nexport type DismissInput = z.infer<typeof dismissInputSchema>\n\nexport async function handleDismiss(input: DismissInput, storage: StorageAdapter) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n const annotation = store?.annotations.find((a) => a.id === input.id)\n if (!annotation) {\n return toolError('annotation_not_found', `Annotation ${input.id} not found in this project`)\n }\n\n let result\n try {\n result = transition(annotation, 'dismiss', { actor: 'agent', reason: input.reason })\n } catch (err) {\n if (err instanceof InvalidTransitionError) {\n return toolError('invalid_transition', err.message, {\n currentStatus: annotation.status,\n requestedTransition: 'dismiss',\n })\n }\n throw err\n }\n\n const updated = {\n ...annotation,\n status: result.status,\n lifecycle: [...annotation.lifecycle, result.event],\n }\n\n try {\n await storage.save(updated)\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n return toolSuccess({ ok: true, status: result.status })\n}\n","import { z } from 'zod'\nimport type { StorageAdapter } from 'web-remarq'\nimport { toolError, toolSuccess } from '../errors'\nimport { toThin } from './list-annotations.js'\n\n/** Waits up to timeoutMs for a backend change. Local mode resolves early on\n * mutation events; cloud mode just sleeps a poll interval and returns false. */\nexport type WaitForChange = (timeoutMs: number) => Promise<boolean>\n\nconst DEFAULT_TIMEOUT_SECONDS = 25\n\nexport const watchAnnotationsInputSchema = z.object({\n timeoutSeconds: z.number().int().min(1).max(120).optional(),\n})\n\nexport type WatchAnnotationsInput = z.infer<typeof watchAnnotationsInputSchema>\n\nexport async function handleWatchAnnotations(\n input: WatchAnnotationsInput,\n storage: StorageAdapter,\n waitForChange: WaitForChange,\n) {\n const timeoutMs = (input.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS) * 1000\n const deadline = Date.now() + timeoutMs\n\n for (;;) {\n let store\n try {\n store = await storage.load()\n } catch (err) {\n return toolError('storage_error', err instanceof Error ? err.message : String(err))\n }\n\n const pending = (store?.annotations ?? []).filter((a) => a.status === 'pending')\n if (pending.length > 0) {\n return toolSuccess({ annotations: pending.map(toThin), total: pending.length, timedOut: false })\n }\n\n const remaining = deadline - Date.now()\n if (remaining <= 0) {\n return toolSuccess({ annotations: [], total: 0, timedOut: true })\n }\n\n await waitForChange(remaining)\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport type { StorageAdapter } from 'web-remarq'\n\nimport { listAnnotationsInputSchema, handleListAnnotations } from './list-annotations.js'\nimport { getAnnotationInputSchema, handleGetAnnotation } from './get-annotation.js'\nimport { acknowledgeInputSchema, handleAcknowledge } from './acknowledge.js'\nimport { claimFixInputSchema, handleClaimFix } from './claim-fix.js'\nimport { dismissInputSchema, handleDismiss } from './dismiss.js'\nimport { watchAnnotationsInputSchema, handleWatchAnnotations, type WaitForChange } from './watch-annotations.js'\n\nexport type { WaitForChange } from './watch-annotations.js'\n\n// Cast helper: our tool results satisfy CallToolResult at runtime; the SDK's\n// inferred type carries an index signature that our narrower types lack.\nfunction cast(p: Promise<{ isError?: boolean; content: Array<{ type: 'text'; text: string }> }>): Promise<CallToolResult> {\n return p as Promise<CallToolResult>\n}\n\nexport function registerTools(\n server: McpServer,\n storage: StorageAdapter,\n opts: { waitForChange: WaitForChange },\n): void {\n server.registerTool(\n 'list_annotations',\n {\n description: 'List annotations in the project with optional filters (route, status, viewport, file). Each item carries a `quality` score (clear | ambiguous | unactionable) when an AI pre-flight check ran.',\n inputSchema: listAnnotationsInputSchema.shape,\n },\n (input) => cast(handleListAnnotations(input, storage)),\n )\n\n server.registerTool(\n 'get_annotation',\n {\n description: 'Get full annotation details including source file:line:col, grep search hints, and the AI quality verdict (`qualityCheck`). If qualityCheck.score is \"ambiguous\" or \"unactionable\", the comment may need designer clarification before fixing — consider dismissing with a reason instead of guessing.',\n inputSchema: getAnnotationInputSchema.shape,\n },\n (input) => cast(handleGetAnnotation(input, storage)),\n )\n\n server.registerTool(\n 'acknowledge',\n {\n description: 'Mark an annotation as in-progress (pending → in_progress).',\n inputSchema: acknowledgeInputSchema.shape,\n },\n (input) => cast(handleAcknowledge(input, storage)),\n )\n\n server.registerTool(\n 'claim_fix',\n {\n description: 'Claim a fix for an annotation (→ fixed_unverified). Human verification still required.',\n inputSchema: claimFixInputSchema.shape,\n },\n (input) => cast(handleClaimFix(input, storage)),\n )\n\n server.registerTool(\n 'dismiss',\n {\n description: 'Dismiss an annotation with an optional reason (terminal state).',\n inputSchema: dismissInputSchema.shape,\n },\n (input) => cast(handleDismiss(input, storage)),\n )\n\n server.registerTool(\n 'watch_annotations',\n {\n description:\n 'Wait for pending annotations (long-poll). Returns immediately if pending annotations exist; otherwise blocks until one appears or timeoutSeconds (default 25) elapses, then returns {\"annotations\": [], \"timedOut\": true}. Call this in a loop to continuously pick up designer feedback. If your environment can run background subagents (e.g. a Task tool), act as a dispatcher: for each returned annotation call acknowledge first (so it stops being redelivered), hand the fix to a background subagent that will claim_fix when done, and return to watch_annotations immediately instead of fixing inline - new feedback must never wait on a fix in progress. Without subagents, acknowledge each annotation before you start fixing it.',\n inputSchema: watchAnnotationsInputSchema.shape,\n },\n (input) => cast(handleWatchAnnotations(input, storage, opts.waitForChange)),\n )\n}\n"],"mappings":"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","names":["z","z","z","z","z","transition","InvalidTransitionError","z","transition","InvalidTransitionError","z","transition","InvalidTransitionError","z","transition","InvalidTransitionError","z","z"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@web-remarq/mcp",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "MCP server for web-remarq
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "MCP server for web-remarq - gives AI agents access to annotations via a local file store or Supabase cloud backend",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
7
7
|
"exports": {
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
48
48
|
"zod": "^3.23.0",
|
|
49
|
-
"@web-remarq/cloud": "^0.
|
|
49
|
+
"@web-remarq/cloud": "^0.3.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"web-remarq": ">=0.
|
|
52
|
+
"web-remarq": ">=0.8.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@types/node": "^20.0.0",
|