crawlmark-mcp 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -309,7 +309,7 @@ Configure your MCP client
309
309
  **Claude Code** (from npm):
310
310
 
311
311
  ```bash
312
- claude mcp add crawlmark -s user -- npx -y crawlmark-mcp
312
+ claude mcp add crawlmark -s user -- npx -y crawlmark-mcp@latest
313
313
  ```
314
314
 
315
315
  **Claude Code** (from source):
@@ -325,18 +325,18 @@ claude mcp add crawlmark -s user -- node --disable-warning=ExperimentalWarning /
325
325
  "mcpServers": {
326
326
  "crawlmark": {
327
327
  "command": "npx",
328
- "args": ["-y", "crawlmark-mcp"]
328
+ "args": ["-y", "crawlmark-mcp@latest"]
329
329
  }
330
330
  }
331
331
  }
332
332
  ```
333
333
 
334
- **Codex** (`~/.codex/config.toml`, or run `codex mcp add crawlmark -- npx -y crawlmark-mcp`):
334
+ **Codex** (`~/.codex/config.toml`, or run `codex mcp add crawlmark -- npx -y crawlmark-mcp@latest`):
335
335
 
336
336
  ```toml
337
337
  [mcp_servers.crawlmark]
338
338
  command = "npx"
339
- args = ["-y", "crawlmark-mcp"]
339
+ args = ["-y", "crawlmark-mcp@latest"]
340
340
  ```
341
341
 
342
342
  For a source checkout, use `"command": "node"` and `"args": ["--disable-warning=ExperimentalWarning", "/absolute/path/to/crawlmark-mcp/dist/index.js"]`. Claude Desktop, Cursor and Codex may not load your shell's `PATH`, so if one can't find `node` or `npx`, use the absolute path (`which node`). Credentials come from `.env` in the config home, so no `env` block is needed unless your settings live somewhere other than the default; then add `"env": { "CRAWLMARK_HOME": "/path/to/home" }`.
@@ -29,9 +29,33 @@ export function cmdLine(cmd, args) {
29
29
  export function isNpxCache(projectRoot) {
30
30
  return projectRoot.split(/[\\/]/).includes("_npx");
31
31
  }
32
+ /** What an npx install registers. `@latest` makes npx check for a newer crawlmark each time an app starts it. */
33
+ export const NPX_SPEC = "crawlmark-mcp@latest";
34
+ /**
35
+ * True when `current` is the entry an earlier crawlmark wrote from an npx install (`npx -y crawlmark-mcp`, no
36
+ * version) and `launch` is the same entry with `@latest`. That's an update of crawlmark's own entry, not a
37
+ * different server, so the wizard offers it with yes as the default.
38
+ */
39
+ export function isNpxLatestUpgrade(current, launch) {
40
+ if (!current || typeof current.command !== "string")
41
+ return false;
42
+ // `claude mcp get` prints the args as one string; app configs keep an array.
43
+ const args = Array.isArray(current.args) ? current.args : typeof current.args === "string" ? current.args.trim().split(/\s+/) : undefined;
44
+ if (!args)
45
+ return false;
46
+ const npx = /(^|[\\/])npx(\.cmd)?$/i;
47
+ if (!npx.test(current.command) || !npx.test(launch.command))
48
+ return false;
49
+ if (JSON.stringify(args) !== JSON.stringify(["-y", "crawlmark-mcp"]) || JSON.stringify(launch.args) !== JSON.stringify(["-y", NPX_SPEC]))
50
+ return false;
51
+ const env = (e) => JSON.stringify(Object.entries((e && typeof e === "object" ? e : {})).sort());
52
+ return env(current.env) === env(launch.env);
53
+ }
54
+ const NPX_UPGRADE_NOTE = "It runs `npx -y crawlmark-mcp`, which keeps reusing the version npx downloaded first.";
55
+ const NPX_UPGRADE_QUESTION = "Update it to `npx -y crawlmark-mcp@latest`, so it picks up new versions?";
32
56
  /**
33
57
  * How an app should start the server. CRAWLMARK_HOME is passed when the home isn't
34
- * the project root. From an npx cache, `npx -y crawlmark-mcp` is registered instead
58
+ * the project root. From an npx cache, `npx -y crawlmark-mcp@latest` is registered instead
35
59
  * of the cache path. `nodeCommand` is `node` (Claude Code, which inherits the shell
36
60
  * PATH) or an absolute path (Claude Desktop, Cursor and Codex, which may not).
37
61
  */
@@ -39,7 +63,7 @@ export function serverLaunch(ctx, nodeCommand) {
39
63
  const env = ctx.paths.homeSource === "project" ? undefined : { CRAWLMARK_HOME: ctx.paths.home };
40
64
  if (isNpxCache(ctx.paths.projectRoot)) {
41
65
  const npx = nodeCommand === "node" ? "npx" : join(dirname(nodeCommand), process.platform === "win32" ? "npx.cmd" : "npx");
42
- return { command: npx, args: ["-y", "crawlmark-mcp"], ...(env ? { env } : {}) };
66
+ return { command: npx, args: ["-y", NPX_SPEC], ...(env ? { env } : {}) };
43
67
  }
44
68
  return { command: nodeCommand, args: ["--disable-warning=ExperimentalWarning", join(ctx.paths.projectRoot, "dist", "index.js")], ...(env ? { env } : {}) };
45
69
  }
@@ -153,11 +177,15 @@ async function registerClaudeCode(ctx, probe, notes) {
153
177
  return "manual";
154
178
  }
155
179
  const scope = current.scope ?? "user";
156
- ctx.out("Claude Code already has a crawlmark server, with a different command:");
180
+ const upgrade = isNpxLatestUpgrade(current, launch);
181
+ if (upgrade)
182
+ ctx.out(`Claude Code has crawlmark registered. ${NPX_UPGRADE_NOTE}`);
183
+ else
184
+ ctx.out("Claude Code already has a crawlmark server, with a different command:");
157
185
  ctx.out(` current: ${describeRegistration(current)}`);
158
186
  ctx.out(` new: ${describeRegistration({ ...launch, scope: "user" })}`);
159
- // Default no, so --yes never removes an existing registration.
160
- if (!(await ctx.prompt.confirm(`Replace it (claude mcp remove ${SERVER_NAME} -s ${scope}, then add)?`, false))) {
187
+ // Default no for a different server, so --yes never removes one; yes for crawlmark's own older npx entry.
188
+ if (!(await ctx.prompt.confirm(upgrade ? NPX_UPGRADE_QUESTION : `Replace it (claude mcp remove ${SERVER_NAME} -s ${scope}, then add)?`, upgrade))) {
161
189
  notes.push({ kind: "info", text: "Kept the existing Claude Code registration." });
162
190
  return "skipped";
163
191
  }
@@ -195,8 +223,15 @@ async function registerJsonApp(ctx, id, notes) {
195
223
  }
196
224
  uiFor(ctx).preview(`${app} config: ${path}`, describeDesktopChange(merge, entry));
197
225
  // Replacing a different crawlmark entry defaults to no, so --yes never replaces one.
198
- const question = merge.replaced ? "Replace the existing crawlmark entry? (the current file is kept as .bak)" : "Write this change? (the current file is kept as .bak)";
199
- if (!(await ctx.prompt.confirm(question, !merge.replaced))) {
226
+ const upgrade = merge.replaced && isNpxLatestUpgrade(merge.previous, entry);
227
+ if (upgrade)
228
+ ctx.out(NPX_UPGRADE_NOTE);
229
+ const question = upgrade
230
+ ? `${NPX_UPGRADE_QUESTION} (the current file is kept as .bak)`
231
+ : merge.replaced
232
+ ? "Replace the existing crawlmark entry? (the current file is kept as .bak)"
233
+ : "Write this change? (the current file is kept as .bak)";
234
+ if (!(await ctx.prompt.confirm(question, upgrade || !merge.replaced))) {
200
235
  notes.push({ kind: "info", text: `Skipped ${app}.` });
201
236
  return "skipped";
202
237
  }
@@ -287,10 +322,14 @@ async function registerCodexCli(ctx, probe, notes) {
287
322
  notes.push({ kind: "warn", text: `To replace it yourself: codex mcp remove ${SERVER_NAME}, then ${display}` });
288
323
  return "manual";
289
324
  }
290
- ctx.out("Codex already has a crawlmark server, with a different command:");
325
+ const upgrade = isNpxLatestUpgrade(state.entry, launch);
326
+ if (upgrade)
327
+ ctx.out(`Codex has crawlmark registered. ${NPX_UPGRADE_NOTE}`);
328
+ else
329
+ ctx.out("Codex already has a crawlmark server, with a different command:");
291
330
  ctx.out(` current: ${describeRegistration(state.entry)}`);
292
331
  ctx.out(` new: ${describeRegistration(want)}`);
293
- if (!(await ctx.prompt.confirm(`Replace it (codex mcp remove ${SERVER_NAME}, then add)?`, false))) {
332
+ if (!(await ctx.prompt.confirm(upgrade ? NPX_UPGRADE_QUESTION : `Replace it (codex mcp remove ${SERVER_NAME}, then add)?`, upgrade))) {
294
333
  notes.push({ kind: "info", text: "Kept the existing Codex registration." });
295
334
  return "skipped";
296
335
  }
@@ -330,8 +369,15 @@ async function registerCodexToml(ctx, notes) {
330
369
  return "already";
331
370
  }
332
371
  uiFor(ctx).preview(`Codex config: ${path}`, describeCodexChange(plan, entry));
333
- const question = plan.replaced ? "Replace the existing crawlmark entry? (the current file is kept as .bak)" : "Write this change? (the current file is kept as .bak)";
334
- if (!(await ctx.prompt.confirm(question, !plan.replaced))) {
372
+ const upgrade = plan.replaced && isNpxLatestUpgrade(plan.previous, serverLaunch(ctx, ctx.node.execPath));
373
+ if (upgrade)
374
+ ctx.out(NPX_UPGRADE_NOTE);
375
+ const question = upgrade
376
+ ? `${NPX_UPGRADE_QUESTION} (the current file is kept as .bak)`
377
+ : plan.replaced
378
+ ? "Replace the existing crawlmark entry? (the current file is kept as .bak)"
379
+ : "Write this change? (the current file is kept as .bak)";
380
+ if (!(await ctx.prompt.confirm(question, upgrade || !plan.replaced))) {
335
381
  notes.push({ kind: "info", text: "Skipped Codex." });
336
382
  return "skipped";
337
383
  }
@@ -370,7 +416,7 @@ export async function registerApps(ctx) {
370
416
  const ui = uiFor(ctx);
371
417
  const notes = [];
372
418
  if (isNpxCache(ctx.paths.projectRoot)) {
373
- ctx.out("crawlmark is running from a temporary npx cache, so Claude will start it with `npx -y crawlmark-mcp` instead of this path.");
419
+ ctx.out("crawlmark is running from a temporary npx cache, so Claude will start it with `npx -y crawlmark-mcp@latest` instead of this path.");
374
420
  }
375
421
  const probes = await ui.spin("Looking for AI apps...", () => detectApps(ctx), { plain: false });
376
422
  const defaults = probes.filter((p) => p.detected).map((p) => p.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crawlmark-mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for automated SEO audits and reports: Google Search Console, GA4, Tag Manager, DataForSEO, Lighthouse and axe-core",
5
5
  "keywords": [
6
6
  "mcp",