@visualsentinel/mcp-server 0.2.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to `@visualsentinel/mcp-server`. Format follows [Keep a Changelog](https://keepachangelog.com/) and the package uses [Semantic Versioning](https://semver.org/).
4
4
 
5
+ ## [0.2.3] - 2026-11-09
6
+
7
+ ### Fixed
8
+
9
+ * API keys and the account tools come with every paid plan (Solo, Starter, Business and Agency), including during the 10-day free trial. The no-key error, the `triage_open_incidents` prompt, the `visualsentinel://help` and `visualsentinel://tools` resources, the README, `llms-install.md`, the `package.json` description, the `server.json` `VS_API_KEY` description and the Gemini, Claude Code and Cursor plugin manifests said Business and Agency only.
10
+ * `visualsentinel://plans` (as of 2026-11-09) now states the current offer: no Free plan for website sign-ups, a free first check of the site, then a 10-day free trial (no card needed with a work email; with a personal email address, a card, $0 today, billed on day 11 unless cancelled) or an immediate subscription, one trial per customer, and the 30-day money-back guarantee. It said 7-day trial, card required. The table adds monthly and yearly prices, gives screenshot checks as sites and pages per site, marks Solo and Starter as having API keys, and lists Asia Pacific among the check regions.
11
+ * The `package.json` description fits the 255 characters registry search shows, so the API key sentence is no longer cut off.
12
+
13
+ ### Changed
14
+
15
+ * The plan wording lives in `src/plan-copy.ts`, shared by the resources, the prompts and the no-key error. Tests pin the offer sentences, the plan table, the description length and the version in every manifest.
16
+
17
+ ## [0.2.2] - 2026-11-06
18
+
19
+ ### Added
20
+
21
+ * Four MCP prompts: `check_website_health`, `investigate_broken_site`, `pre_launch_checklist`, `triage_open_incidents`.
22
+ * Three read-only MCP resources: `visualsentinel://plans`, `visualsentinel://help`, `visualsentinel://tools`. The server now advertises the `prompts` and `resources` capabilities.
23
+ * LobeHub badge and homepage link in the README (the LobeHub claim route).
24
+ * `mcpName` is now `com.visualsentinel/mcp-server` (the registry namespace is verified through a DNS record on visualsentinel.com).
25
+
26
+ ### Changed
27
+
28
+ * `package.json` `homepage` is now https://visualsentinel.com/mcp, so npm and its mirrors link to the product page instead of this repository.
29
+ * Account tools called without an API key now explain that API keys are available on the Business and Agency plans and that the five public tools work without one.
30
+ * `SERVER_VERSION` lives in `src/version.ts`; a test keeps it equal to `package.json`.
31
+
5
32
  ## [0.2.1] - 2026-10-16
6
33
 
7
34
  ### Fixed
package/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # Visual Sentinel MCP Server
2
2
 
3
+ [![MCP Badge](https://lobehub.com/badge/mcp/visualsentinel-mcp-server)](https://lobehub.com/mcp/visualsentinel-mcp-server)
4
+
3
5
  Model Context Protocol server for [Visual Sentinel](https://visualsentinel.com). Visual Sentinel catches pages that look broken even when the site is up. It monitors six layers: uptime, performance, SSL, DNS, visual regression, and content change.
4
6
 
5
- Connect Claude Desktop, Claude Code, Cursor, Cline, Continue, or any MCP-compatible client so an AI assistant can run DNS, SSL and speed checks on any site with no account. With a Visual Sentinel account and an API key (Business and Agency plans) it can also list and create monitors, fetch incident history, and acknowledge alerts on your behalf.
7
+ Connect Claude Desktop, Claude Code, Cursor, Cline, Continue, or any MCP-compatible client so an AI assistant can run DNS, SSL and speed checks on any site with no account. With a Visual Sentinel account and an API key (every paid plan, including the 10-day free trial) it can also list and create monitors, fetch incident history, and acknowledge alerts on your behalf.
6
8
 
9
+ * **Homepage and docs:** https://visualsentinel.com/mcp
7
10
  * **npm:** `@visualsentinel/mcp-server`
8
11
  * **OpenAPI spec:** https://github.com/VisualSentinel/openapi
9
- * **API key (Business and Agency plans):** https://visualsentinel.com/settings/api-keys
12
+ * **API key (every paid plan, including the 10-day free trial):** https://visualsentinel.com/settings/api-keys
10
13
  * **Hosted API reference:** https://visualsentinel.com/docs
11
14
 
12
15
  ## What you get
@@ -40,6 +43,25 @@ Connect Claude Desktop, Claude Code, Cursor, Cline, Continue, or any MCP-compati
40
43
  | `vs_status_pages_list` | `GET /api/status-pages` | List your public status pages |
41
44
  | `vs_servers_list` | `GET /api/servers` | List registered origin servers |
42
45
 
46
+ ## Prompts and resources
47
+
48
+ Besides the 17 tools, the server offers four ready-made prompts and three read-only resources. None of them need an API key except the last prompt.
49
+
50
+ | Prompt | What it does |
51
+ |---|---|
52
+ | `check_website_health` | Runs the website, SSL, DNS and speed checks on one site and summarises what needs attention. |
53
+ | `investigate_broken_site` | Works through DNS, certificate, response and speed to find the layer at fault; reads incidents and alerts too when an API key is set. |
54
+ | `pre_launch_checklist` | Pass, Warn or Fail checklist for a new or migrated domain. |
55
+ | `triage_open_incidents` | Ranks open incidents and unacknowledged alerts (needs an API key). |
56
+
57
+ | Resource | Contents |
58
+ |---|---|
59
+ | `visualsentinel://plans` | Prices, plan limits, the trial terms, and which plans include API keys and the account tools. |
60
+ | `visualsentinel://help` | Setup without an account, adding an API key, links. |
61
+ | `visualsentinel://tools` | Every tool, whether it needs a key, and whether it changes data. |
62
+
63
+ Without an API key the server still starts and lists everything. The five public tools work; each account tool returns a clear message that it needs an API key (every paid plan, including the 10-day free trial) instead of failing.
64
+
43
65
  ## Installation
44
66
 
45
67
  ### Claude Desktop
@@ -140,7 +162,7 @@ Two environment variables, both optional except in the noted cases:
140
162
 
141
163
  | Variable | Default | Purpose |
142
164
  |---|---|---|
143
- | `VS_API_KEY` | (none) | Required for the 12 authenticated tools (API keys are available on the Business and Agency plans). Public tools work without it. Generate at https://visualsentinel.com/settings/api-keys. |
165
+ | `VS_API_KEY` | (none) | Required for the 12 authenticated tools (API keys come with every paid plan, including during the 10-day free trial). Public tools work without it. Generate at https://visualsentinel.com/settings/api-keys. |
144
166
  | `VS_API_BASE` | `https://visualsentinel.com` | Override only for testing against a local development instance. |
145
167
 
146
168
  If a tool that needs auth is called without `VS_API_KEY`, the server returns a clear error rather than silently failing.
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/plan-copy.ts
4
+ var API_KEY_PLANS = "every paid plan (Solo, Starter, Business and Agency), including during the 10-day free trial";
5
+
3
6
  // src/api-client.ts
4
7
  var DEFAULT_BASE = "https://visualsentinel.com";
5
8
  var VsApiError = class extends Error {
@@ -31,7 +34,7 @@ var ApiClient = class {
31
34
  const auth = options.auth ?? true;
32
35
  if (auth && !this.hasApiKey()) {
33
36
  throw new VsApiError(
34
- "Visual Sentinel API key required. Set VS_API_KEY (https://visualsentinel.com/settings/api-keys).",
37
+ `This tool needs a Visual Sentinel API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys and set it as VS_API_KEY. The DNS, SSL, speed, website-check and health tools work without a key.`,
35
38
  401,
36
39
  null
37
40
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/api-client.ts"],"sourcesContent":["/**\n * Tiny HTTP client for the Visual Sentinel public API.\n *\n * Reads the API key from `VS_API_KEY` (or `VISUALSENTINEL_API_KEY`) once at\n * construction time. All requests are routed at `https://visualsentinel.com`\n * unless `VS_API_BASE` overrides it (useful for testing against a local\n * dev instance).\n */\n\nconst DEFAULT_BASE = 'https://visualsentinel.com';\n\nexport interface ApiClientOptions {\n apiKey?: string | undefined;\n baseUrl?: string | undefined;\n userAgent?: string;\n}\n\nexport class VsApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n this.name = 'VsApiError';\n }\n}\n\nexport class ApiClient {\n private readonly apiKey: string | undefined;\n private readonly baseUrl: string;\n private readonly userAgent: string;\n\n constructor(opts: ApiClientOptions = {}) {\n this.apiKey =\n opts.apiKey ??\n process.env.VS_API_KEY ??\n process.env.VISUALSENTINEL_API_KEY ??\n undefined;\n this.baseUrl = (opts.baseUrl ?? process.env.VS_API_BASE ?? DEFAULT_BASE).replace(\n /\\/+$/,\n '',\n );\n this.userAgent = opts.userAgent ?? '@visualsentinel/mcp-server';\n }\n\n hasApiKey(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey.length > 0;\n }\n\n async request<T = unknown>(\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n path: string,\n options: { query?: Record<string, string | number | boolean | undefined>; body?: unknown; auth?: boolean } = {},\n ): Promise<T> {\n const auth = options.auth ?? true;\n if (auth && !this.hasApiKey()) {\n throw new VsApiError(\n 'Visual Sentinel API key required. Set VS_API_KEY (https://visualsentinel.com/settings/api-keys).',\n 401,\n null,\n );\n }\n\n const url = new URL(path.startsWith('/') ? path : `/${path}`, this.baseUrl);\n if (options.query) {\n for (const [k, v] of Object.entries(options.query)) {\n if (v !== undefined && v !== null) url.searchParams.set(k, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'User-Agent': this.userAgent,\n };\n if (auth && this.apiKey) headers['X-API-Key'] = this.apiKey;\n if (options.body !== undefined) headers['Content-Type'] = 'application/json';\n\n const res = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n });\n\n const text = await res.text();\n let parsed: unknown = null;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n throw new VsApiError(\n `Visual Sentinel API ${method} ${path} returned ${res.status}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n}\n"],"mappings":";;;AASA,IAAM,eAAe;AAQd,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACgB,QACA,MAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAEO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,OAAyB,CAAC,GAAG;AACvC,SAAK,SACH,KAAK,UACL,QAAQ,IAAI,cACZ,QAAQ,IAAI,0BACZ;AACF,SAAK,WAAW,KAAK,WAAW,QAAQ,IAAI,eAAe,cAAc;AAAA,MACvE;AAAA,MACA;AAAA,IACF;AACA,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS;AAAA,EACjE;AAAA,EAEA,MAAM,QACJ,QACA,MACA,UAA6G,CAAC,GAClG;AACZ,UAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAI,QAAQ,CAAC,KAAK,UAAU,GAAG;AAC7B,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,IAAI,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,IAAI,KAAK,OAAO;AAC1E,QAAI,QAAQ,OAAO;AACjB,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AAClD,YAAI,MAAM,UAAa,MAAM,KAAM,KAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MACtE;AAAA,IACF;AAEA,UAAM,UAAkC;AAAA,MACtC,QAAQ;AAAA,MACR,cAAc,KAAK;AAAA,IACrB;AACA,QAAI,QAAQ,KAAK,OAAQ,SAAQ,WAAW,IAAI,KAAK;AACrD,QAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,IAAI;AAE1D,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,MAAM,QAAQ,SAAS,SAAY,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,IACpE,CAAC;AAED,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,SAAkB;AACtB,QAAI,KAAK,SAAS,GAAG;AACnB,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI;AAAA,MAC1B,QAAQ;AACN,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,uBAAuB,MAAM,IAAI,IAAI,aAAa,IAAI,MAAM;AAAA,QAC5D,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/plan-copy.ts","../src/api-client.ts"],"sourcesContent":["/**\n * Plan wording shared by the resources, the prompts and the no-key error, so\n * the plan statement lives in one place. It mirrors the Visual Sentinel offer\n * as of PLANS_AS_OF in resources.ts; https://visualsentinel.com/pricing is\n * always current.\n *\n * API keys (and so the account tools) come with every paid plan. A trial of a\n * paid plan gets that plan's API access, with or without a card. A workspace\n * on the older Free plan, one that has not started a trial or subscription,\n * and one whose monitoring is paused cannot use its keys.\n */\n\n/** Finishes \"API keys come with ...\". */\nexport const API_KEY_PLANS =\n 'every paid plan (Solo, Starter, Business and Agency), including during the 10-day free trial';\n\n/** The same, short, for parentheses and table cells. */\nexport const API_KEY_PLANS_SHORT = 'every paid plan, including the 10-day free trial';\n\nexport const NO_FREE_PLAN_SENTENCE = 'Sign-ups on the website have no Free plan.';\n\nexport const MONEY_BACK_SENTENCE = 'Every paid plan has a 30-day money-back guarantee from the first payment.';\n","/**\n * Tiny HTTP client for the Visual Sentinel public API.\n *\n * Reads the API key from `VS_API_KEY` (or `VISUALSENTINEL_API_KEY`) once at\n * construction time. All requests are routed at `https://visualsentinel.com`\n * unless `VS_API_BASE` overrides it (useful for testing against a local\n * dev instance).\n */\n\nimport { API_KEY_PLANS } from './plan-copy.js';\n\nconst DEFAULT_BASE = 'https://visualsentinel.com';\n\nexport interface ApiClientOptions {\n apiKey?: string | undefined;\n baseUrl?: string | undefined;\n userAgent?: string;\n}\n\nexport class VsApiError extends Error {\n constructor(\n message: string,\n public readonly status: number,\n public readonly body: unknown,\n ) {\n super(message);\n this.name = 'VsApiError';\n }\n}\n\nexport class ApiClient {\n private readonly apiKey: string | undefined;\n private readonly baseUrl: string;\n private readonly userAgent: string;\n\n constructor(opts: ApiClientOptions = {}) {\n this.apiKey =\n opts.apiKey ??\n process.env.VS_API_KEY ??\n process.env.VISUALSENTINEL_API_KEY ??\n undefined;\n this.baseUrl = (opts.baseUrl ?? process.env.VS_API_BASE ?? DEFAULT_BASE).replace(\n /\\/+$/,\n '',\n );\n this.userAgent = opts.userAgent ?? '@visualsentinel/mcp-server';\n }\n\n hasApiKey(): boolean {\n return typeof this.apiKey === 'string' && this.apiKey.length > 0;\n }\n\n async request<T = unknown>(\n method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',\n path: string,\n options: { query?: Record<string, string | number | boolean | undefined>; body?: unknown; auth?: boolean } = {},\n ): Promise<T> {\n const auth = options.auth ?? true;\n if (auth && !this.hasApiKey()) {\n throw new VsApiError(\n `This tool needs a Visual Sentinel API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys and set it as VS_API_KEY. The DNS, SSL, speed, website-check and health tools work without a key.`,\n 401,\n null,\n );\n }\n\n const url = new URL(path.startsWith('/') ? path : `/${path}`, this.baseUrl);\n if (options.query) {\n for (const [k, v] of Object.entries(options.query)) {\n if (v !== undefined && v !== null) url.searchParams.set(k, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'User-Agent': this.userAgent,\n };\n if (auth && this.apiKey) headers['X-API-Key'] = this.apiKey;\n if (options.body !== undefined) headers['Content-Type'] = 'application/json';\n\n const res = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n });\n\n const text = await res.text();\n let parsed: unknown = null;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n throw new VsApiError(\n `Visual Sentinel API ${method} ${path} returned ${res.status}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n}\n"],"mappings":";;;AAaO,IAAM,gBACX;;;ACHF,IAAM,eAAe;AAQd,IAAM,aAAN,cAAyB,MAAM;AAAA,EACpC,YACE,SACgB,QACA,MAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA,EALkB;AAAA,EACA;AAKpB;AAEO,IAAM,YAAN,MAAgB;AAAA,EACJ;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,OAAyB,CAAC,GAAG;AACvC,SAAK,SACH,KAAK,UACL,QAAQ,IAAI,cACZ,QAAQ,IAAI,0BACZ;AACF,SAAK,WAAW,KAAK,WAAW,QAAQ,IAAI,eAAe,cAAc;AAAA,MACvE;AAAA,MACA;AAAA,IACF;AACA,SAAK,YAAY,KAAK,aAAa;AAAA,EACrC;AAAA,EAEA,YAAqB;AACnB,WAAO,OAAO,KAAK,WAAW,YAAY,KAAK,OAAO,SAAS;AAAA,EACjE;AAAA,EAEA,MAAM,QACJ,QACA,MACA,UAA6G,CAAC,GAClG;AACZ,UAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAI,QAAQ,CAAC,KAAK,UAAU,GAAG;AAC7B,YAAM,IAAI;AAAA,QACR,iEAAiE,aAAa;AAAA,QAC9E;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,IAAI,IAAI,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,IAAI,KAAK,OAAO;AAC1E,QAAI,QAAQ,OAAO;AACjB,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,KAAK,GAAG;AAClD,YAAI,MAAM,UAAa,MAAM,KAAM,KAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;AAAA,MACtE;AAAA,IACF;AAEA,UAAM,UAAkC;AAAA,MACtC,QAAQ;AAAA,MACR,cAAc,KAAK;AAAA,IACrB;AACA,QAAI,QAAQ,KAAK,OAAQ,SAAQ,WAAW,IAAI,KAAK;AACrD,QAAI,QAAQ,SAAS,OAAW,SAAQ,cAAc,IAAI;AAE1D,UAAM,MAAM,MAAM,MAAM,KAAK;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,MAAM,QAAQ,SAAS,SAAY,KAAK,UAAU,QAAQ,IAAI,IAAI;AAAA,IACpE,CAAC;AAED,UAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,QAAI,SAAkB;AACtB,QAAI,KAAK,SAAS,GAAG;AACnB,UAAI;AACF,iBAAS,KAAK,MAAM,IAAI;AAAA,MAC1B,QAAQ;AACN,iBAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI;AAAA,QACR,uBAAuB,MAAM,IAAI,IAAI,aAAa,IAAI,MAAM;AAAA,QAC5D,IAAI;AAAA,QACJ;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AACF;","names":[]}
package/dist/index.js CHANGED
@@ -5,9 +5,21 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import {
7
7
  CallToolRequestSchema,
8
- ListToolsRequestSchema
8
+ GetPromptRequestSchema,
9
+ ListPromptsRequestSchema,
10
+ ListResourcesRequestSchema,
11
+ ListToolsRequestSchema,
12
+ McpError,
13
+ ErrorCode,
14
+ ReadResourceRequestSchema
9
15
  } from "@modelcontextprotocol/sdk/types.js";
10
16
 
17
+ // src/plan-copy.ts
18
+ var API_KEY_PLANS = "every paid plan (Solo, Starter, Business and Agency), including during the 10-day free trial";
19
+ var API_KEY_PLANS_SHORT = "every paid plan, including the 10-day free trial";
20
+ var NO_FREE_PLAN_SENTENCE = "Sign-ups on the website have no Free plan.";
21
+ var MONEY_BACK_SENTENCE = "Every paid plan has a 30-day money-back guarantee from the first payment.";
22
+
11
23
  // src/api-client.ts
12
24
  var DEFAULT_BASE = "https://visualsentinel.com";
13
25
  var VsApiError = class extends Error {
@@ -39,7 +51,7 @@ var ApiClient = class {
39
51
  const auth = options.auth ?? true;
40
52
  if (auth && !this.hasApiKey()) {
41
53
  throw new VsApiError(
42
- "Visual Sentinel API key required. Set VS_API_KEY (https://visualsentinel.com/settings/api-keys).",
54
+ `This tool needs a Visual Sentinel API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys and set it as VS_API_KEY. The DNS, SSL, speed, website-check and health tools work without a key.`,
43
55
  401,
44
56
  null
45
57
  );
@@ -189,7 +201,7 @@ var TOOLS = [
189
201
  idempotentHint: true,
190
202
  openWorldHint: true
191
203
  },
192
- description: "Run a one-shot performance check against a URL: TTFB, total load time, transfer size, status code, and redirect chain. No authentication required.",
204
+ description: "Run a one-shot Lighthouse lab test on a URL. Returns the performance score (0 to 100) plus Largest Contentful Paint, First Contentful Paint, Cumulative Layout Shift, Time to First Byte, Total Blocking Time and Speed Index (times in milliseconds). No account needed.",
193
205
  inputSchema: {
194
206
  type: "object",
195
207
  properties: {
@@ -558,7 +570,7 @@ var TOOLS = [
558
570
  idempotentHint: true,
559
571
  openWorldHint: false
560
572
  },
561
- description: "Acknowledge an alert by id. Acknowledgement is recorded with the calling API key's user.",
573
+ description: "Acknowledge an alert by id. The acknowledgement is recorded against the account that authorised this connection.",
562
574
  inputSchema: {
563
575
  type: "object",
564
576
  properties: {
@@ -621,9 +633,196 @@ function findTool(name) {
621
633
  return TOOLS.find((t) => t.name === name);
622
634
  }
623
635
 
636
+ // src/prompts.ts
637
+ function need(args, key) {
638
+ const v = args[key]?.trim();
639
+ if (!v) throw new Error(`Argument "${key}" is required.`);
640
+ return v;
641
+ }
642
+ function hostOf(value) {
643
+ return value.replace(/^https?:\/\//i, "").replace(/\/.*$/, "");
644
+ }
645
+ var PROMPTS = [
646
+ {
647
+ name: "check_website_health",
648
+ title: "Check a website's health",
649
+ description: "Run uptime, SSL, DNS and speed checks on one site and summarise what needs attention. No account or API key needed.",
650
+ arguments: [
651
+ { name: "url", description: "Site to check, e.g. https://example.com", required: true }
652
+ ],
653
+ build: (args) => {
654
+ const url = need(args, "url");
655
+ const host = hostOf(url);
656
+ return [
657
+ `Check the health of ${url} with the Visual Sentinel tools and report back in plain language.`,
658
+ "",
659
+ `1. vs_website_check with url "${url}": HTTP status, response time, server header.`,
660
+ `2. vs_ssl_check with domain "${host}": certificate validity, expiry date and chain.`,
661
+ `3. vs_dns_check with domain "${host}": A, AAAA, MX, NS and TXT records.`,
662
+ `4. vs_speed_test with url "${url}": time to first byte, total load time, transfer size, redirects.`,
663
+ "",
664
+ "Then give: a one-line verdict, a short list of anything that looks wrong (non-200 status, certificate close to expiry, missing or odd DNS records, slow response, long redirect chain), and the one fix to do first. Say which checks succeeded and which returned an error. Do not guess at anything a tool did not return."
665
+ ].join("\n");
666
+ }
667
+ },
668
+ {
669
+ name: "investigate_broken_site",
670
+ title: "Investigate why my site looks broken",
671
+ description: "Work out why a site is down, slow or looking wrong by checking each layer in order. With an API key it also reads open incidents and recent alerts.",
672
+ arguments: [
673
+ { name: "url", description: "The affected page, e.g. https://example.com/pricing", required: true },
674
+ { name: "symptom", description: 'What you see, e.g. "blank page", "images missing", "very slow"', required: false }
675
+ ],
676
+ build: (args) => {
677
+ const url = need(args, "url");
678
+ const host = hostOf(url);
679
+ const symptom = args.symptom?.trim();
680
+ return [
681
+ `${symptom ? `The page ${url} shows this problem: ${symptom}.` : `Something looks wrong on ${url}.`} Find the most likely layer at fault, using the Visual Sentinel tools in this order and stopping to report as soon as you find a clear cause:`,
682
+ "",
683
+ `1. DNS: vs_dns_check for "${host}". Do the records resolve, and do they point where they should?`,
684
+ `2. TLS: vs_ssl_check for "${host}". Is the certificate valid, unexpired and issued for this hostname?`,
685
+ `3. Server response: vs_website_check for "${url}". Status code, response time, content snippet.`,
686
+ `4. Speed: vs_speed_test for "${url}". Is time to first byte or load time unusually high, and is there a redirect chain?`,
687
+ "5. If a Visual Sentinel API key is configured: vs_monitors_list to find a monitor for this site, then vs_incidents_list and vs_alerts_list for it, and vs_monitors_uptime for the last 24 hours. If no key is set, say that account data was not checked.",
688
+ "",
689
+ "A page can return HTTP 200 and still look broken (missing styles, empty content, a failed script). If every check above is healthy, say so and suggest comparing the page against an earlier screenshot, which is what Visual Sentinel visual monitoring does.",
690
+ "Report: the evidence from each step, the most likely cause, and the next action."
691
+ ].join("\n");
692
+ }
693
+ },
694
+ {
695
+ name: "pre_launch_checklist",
696
+ title: "Pre-launch checklist for a site",
697
+ description: "Check a new or migrated site before it goes live: DNS, certificate, response and speed, then a short list of fixes. No API key needed.",
698
+ arguments: [
699
+ { name: "domain", description: "The domain being launched, e.g. example.com", required: true }
700
+ ],
701
+ build: (args) => {
702
+ const domain = hostOf(need(args, "domain"));
703
+ return [
704
+ `Run a pre-launch check on ${domain} with the Visual Sentinel tools and return a checklist with Pass, Warn or Fail for each line.`,
705
+ "",
706
+ `- DNS: vs_dns_check for "${domain}" and for "www.${domain}". Both resolve, and the records point to the intended host. Check MX and TXT (SPF) records if the domain sends email.`,
707
+ `- Certificate: vs_ssl_check for "${domain}" and for "www.${domain}". Valid, issued for the hostname, and more than 30 days from expiry.`,
708
+ `- Response: vs_website_check for "https://${domain}" and "https://www.${domain}". Both return 200 or one cleanly redirects to the other.`,
709
+ `- Speed: vs_speed_test for "https://${domain}". Report time to first byte, total load time and redirect count.`,
710
+ "",
711
+ "Finish with the Fail items first, each with a concrete fix, then the Warn items. Add one line suggesting monitors to create after launch (uptime on the home page, certificate expiry, DNS records), and say that creating them needs a Visual Sentinel account with an API key. Do not create anything unless asked."
712
+ ].join("\n");
713
+ }
714
+ },
715
+ {
716
+ name: "triage_open_incidents",
717
+ title: "Triage open incidents",
718
+ description: `Review open incidents and unacknowledged alerts on your account and rank what to look at first. Needs a Visual Sentinel API key (${API_KEY_PLANS_SHORT}).`,
719
+ arguments: [],
720
+ build: () => [
721
+ "Review my Visual Sentinel account and tell me what needs attention first.",
722
+ "",
723
+ "1. vs_incidents_list with status OPEN, then vs_incidents_get for each one (up to 5, most recent first).",
724
+ "2. vs_alerts_list with status UNACKNOWLEDGED.",
725
+ "3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.",
726
+ "",
727
+ `Rank the incidents by customer impact (production sites and checkout pages before staging), group alerts that belong to the same incident, and give one suggested next step for each. If the tools return an "API key required" error, tell me that account tools need an API key, which comes with ${API_KEY_PLANS}, and stop. Ask before acknowledging any alert.`
728
+ ].join("\n")
729
+ }
730
+ ];
731
+ function findPrompt(name) {
732
+ return PROMPTS.find((p) => p.name === name);
733
+ }
734
+
735
+ // src/version.ts
736
+ var SERVER_VERSION = "0.2.3";
737
+
738
+ // src/resources.ts
739
+ var PLANS_AS_OF = "2026-11-09";
740
+ var PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})
741
+
742
+ Prices and the current list are always at https://visualsentinel.com/pricing. ${NO_FREE_PLAN_SENTENCE} A new workspace gets a free first check of its site, then a 10-day free trial of a paid plan or an immediate subscription. No card is needed for the trial with a work email. With a personal email address, you add a card ($0 today, billed on day 11 unless you cancel). When a trial without a card ends, monitoring pauses until you subscribe; your sites, history and settings are kept. One trial per customer. ${MONEY_BACK_SENTENCE} Workspaces that already had the Free plan keep it.
743
+
744
+ Prices are in US dollars before tax. Yearly costs 10 months of monthly.
745
+
746
+ | Plan | Price | Monitors | Fastest uptime check | Sites with screenshot checks | Status pages | API keys and MCP account tools |
747
+ |---|---|---|---|---|---|---|
748
+ | Solo | $15 a month or $150 a year | 10 | every 5 minutes | 1 site, up to 4 pages, every 30 minutes | 1 | yes |
749
+ | Starter | $30 a month or $300 a year | 15 | every 5 minutes | 5 sites, up to 6 pages each, every 15 minutes | 1 | yes |
750
+ | Business | $80 a month or $800 a year | 60 | every minute | 60 sites, up to 6 pages each, every 15 minutes | 5 | yes |
751
+ | Agency | $300 a month or $3,000 a year | 300 | every minute | 300 sites, up to 6 pages each, every 15 minutes | 50 | yes |
752
+
753
+ - A site with screenshot checks is the URL you add plus the extra pages your plan includes. It also gets uptime checks and counts toward the monitor total. Content checks start on Starter.
754
+ - The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.
755
+ - The twelve account tools need an API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys.
756
+ - The older Free plan has no API keys. Keys also stop working while the workspace has no active plan or trial (for example after a trial without a card ends, or while a subscription is paused) and work again once a paid plan is active.
757
+ - Checks run from Europe and the US, plus Asia Pacific on every paid plan. Alert channels on every paid plan: email, Slack, Discord, Microsoft Teams, Telegram, PagerDuty, Opsgenie, webhooks, WhatsApp, Prometheus, and push notifications in the mobile app.
758
+ `;
759
+ var HELP = `# Using the Visual Sentinel MCP server
760
+
761
+ Visual Sentinel catches pages that look broken even when the site is up. It watches uptime, performance, SSL certificates, DNS, visual changes and content changes.
762
+
763
+ ## Start without an account
764
+ Ask for a DNS lookup, an SSL certificate check, a speed test or a website check on any public site. These five tools need no account or key.
765
+
766
+ ## Connect your account
767
+ 1. Get an API key (${API_KEY_PLANS_SHORT}): https://visualsentinel.com/settings/api-keys
768
+ 2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.
769
+ 3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.
770
+
771
+ ## Ready-made prompts
772
+ check_website_health, investigate_broken_site, pre_launch_checklist and triage_open_incidents.
773
+
774
+ ## Links
775
+ - Docs and install: https://visualsentinel.com/mcp
776
+ - API reference: https://visualsentinel.com/docs
777
+ - Pricing: https://visualsentinel.com/pricing
778
+ - Source: https://github.com/VisualSentinel/mcp-server
779
+ - Support: https://visualsentinel.com/contact
780
+ `;
781
+ function toolsReference() {
782
+ const rows = TOOLS.map(
783
+ (t) => `| ${t.name} | ${t.requiresAuth ? "API key (any paid plan or trial)" : "none"} | ${t.annotations.readOnlyHint ? "read-only" : "changes data"} | ${(t.description.split(". ")[0] ?? "").replace(/\.$/, "")} |`
784
+ );
785
+ return [
786
+ "# Visual Sentinel tools",
787
+ "",
788
+ "| Tool | Needs | Type | What it does |",
789
+ "|---|---|---|---|",
790
+ ...rows,
791
+ ""
792
+ ].join("\n");
793
+ }
794
+ var RESOURCES = [
795
+ {
796
+ uri: "visualsentinel://plans",
797
+ name: "plans",
798
+ title: "Plans and limits",
799
+ description: "Monitor counts, check frequencies and which plans include API keys and the MCP account tools.",
800
+ mimeType: "text/markdown",
801
+ read: () => PLANS
802
+ },
803
+ {
804
+ uri: "visualsentinel://help",
805
+ name: "help",
806
+ title: "Help and setup",
807
+ description: "How to use the server without an account, how to add an API key, and where the docs are.",
808
+ mimeType: "text/markdown",
809
+ read: () => HELP
810
+ },
811
+ {
812
+ uri: "visualsentinel://tools",
813
+ name: "tools",
814
+ title: "Tool reference",
815
+ description: "Every tool, whether it needs an API key, and whether it is read-only.",
816
+ mimeType: "text/markdown",
817
+ read: toolsReference
818
+ }
819
+ ];
820
+ function findResource(uri) {
821
+ return RESOURCES.find((r) => r.uri === uri);
822
+ }
823
+
624
824
  // src/index.ts
625
825
  var SERVER_NAME = "visualsentinel";
626
- var SERVER_VERSION = "0.2.1";
627
826
  function toMcpToolDescriptor(tool) {
628
827
  return {
629
828
  name: tool.name,
@@ -637,11 +836,47 @@ async function main() {
637
836
  const client = new ApiClient();
638
837
  const server = new Server(
639
838
  { name: SERVER_NAME, version: SERVER_VERSION },
640
- { capabilities: { tools: {} } }
839
+ { capabilities: { tools: {}, prompts: {}, resources: {} } }
641
840
  );
642
841
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
643
842
  tools: TOOLS.map(toMcpToolDescriptor)
644
843
  }));
844
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
845
+ prompts: PROMPTS.map((p) => ({
846
+ name: p.name,
847
+ title: p.title,
848
+ description: p.description,
849
+ arguments: p.arguments
850
+ }))
851
+ }));
852
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
853
+ const prompt = findPrompt(req.params.name);
854
+ if (!prompt) throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${req.params.name}`);
855
+ let text;
856
+ try {
857
+ text = prompt.build(req.params.arguments ?? {});
858
+ } catch (err) {
859
+ throw new McpError(ErrorCode.InvalidParams, err instanceof Error ? err.message : String(err));
860
+ }
861
+ return {
862
+ description: prompt.description,
863
+ messages: [{ role: "user", content: { type: "text", text } }]
864
+ };
865
+ });
866
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
867
+ resources: RESOURCES.map((r) => ({
868
+ uri: r.uri,
869
+ name: r.name,
870
+ title: r.title,
871
+ description: r.description,
872
+ mimeType: r.mimeType
873
+ }))
874
+ }));
875
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
876
+ const resource = findResource(req.params.uri);
877
+ if (!resource) throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${req.params.uri}`);
878
+ return { contents: [{ uri: resource.uri, mimeType: resource.mimeType, text: resource.read() }] };
879
+ });
645
880
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
646
881
  const tool = findTool(req.params.name);
647
882
  if (!tool) {