@visualsentinel/mcp-server 0.2.2 → 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,18 @@
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
+
5
17
  ## [0.2.2] - 2026-11-06
6
18
 
7
19
  ### Added
package/README.md CHANGED
@@ -4,12 +4,12 @@
4
4
 
5
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.
6
6
 
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 (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.
8
8
 
9
9
  * **Homepage and docs:** https://visualsentinel.com/mcp
10
10
  * **npm:** `@visualsentinel/mcp-server`
11
11
  * **OpenAPI spec:** https://github.com/VisualSentinel/openapi
12
- * **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
13
13
  * **Hosted API reference:** https://visualsentinel.com/docs
14
14
 
15
15
  ## What you get
@@ -56,11 +56,11 @@ Besides the 17 tools, the server offers four ready-made prompts and three read-o
56
56
 
57
57
  | Resource | Contents |
58
58
  |---|---|
59
- | `visualsentinel://plans` | Plan limits, and which plans include API keys and the account tools. |
59
+ | `visualsentinel://plans` | Prices, plan limits, the trial terms, and which plans include API keys and the account tools. |
60
60
  | `visualsentinel://help` | Setup without an account, adding an API key, links. |
61
61
  | `visualsentinel://tools` | Every tool, whether it needs a key, and whether it changes data. |
62
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 (Business and Agency plans) instead of failing.
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
64
 
65
65
  ## Installation
66
66
 
@@ -162,7 +162,7 @@ Two environment variables, both optional except in the noted cases:
162
162
 
163
163
  | Variable | Default | Purpose |
164
164
  |---|---|---|
165
- | `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. |
166
166
  | `VS_API_BASE` | `https://visualsentinel.com` | Override only for testing against a local development instance. |
167
167
 
168
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
- "This tool needs a Visual Sentinel API key, which is available on the Business and Agency 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.",
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 'This tool needs a Visual Sentinel API key, which is available on the Business and Agency 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":";;;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
@@ -14,6 +14,12 @@ import {
14
14
  ReadResourceRequestSchema
15
15
  } from "@modelcontextprotocol/sdk/types.js";
16
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
+
17
23
  // src/api-client.ts
18
24
  var DEFAULT_BASE = "https://visualsentinel.com";
19
25
  var VsApiError = class extends Error {
@@ -45,7 +51,7 @@ var ApiClient = class {
45
51
  const auth = options.auth ?? true;
46
52
  if (auth && !this.hasApiKey()) {
47
53
  throw new VsApiError(
48
- "This tool needs a Visual Sentinel API key, which is available on the Business and Agency 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.",
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.`,
49
55
  401,
50
56
  null
51
57
  );
@@ -709,7 +715,7 @@ var PROMPTS = [
709
715
  {
710
716
  name: "triage_open_incidents",
711
717
  title: "Triage open incidents",
712
- description: "Review open incidents and unacknowledged alerts on your account and rank what to look at first. Needs a Visual Sentinel API key (Business and Agency plans).",
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}).`,
713
719
  arguments: [],
714
720
  build: () => [
715
721
  "Review my Visual Sentinel account and tell me what needs attention first.",
@@ -718,7 +724,7 @@ var PROMPTS = [
718
724
  "2. vs_alerts_list with status UNACKNOWLEDGED.",
719
725
  "3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.",
720
726
  "",
721
- '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 from the Business or Agency plan, and stop. Ask before acknowledging any alert.'
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.`
722
728
  ].join("\n")
723
729
  }
724
730
  ];
@@ -727,24 +733,28 @@ function findPrompt(name) {
727
733
  }
728
734
 
729
735
  // src/version.ts
730
- var SERVER_VERSION = "0.2.2";
736
+ var SERVER_VERSION = "0.2.3";
731
737
 
732
738
  // src/resources.ts
733
- var PLANS_AS_OF = "2026-10-16";
739
+ var PLANS_AS_OF = "2026-11-09";
734
740
  var PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})
735
741
 
736
- Prices and the current list are always at https://visualsentinel.com/pricing. There is no free plan for new accounts: a new workspace gets a first check of its site, then a 7-day trial (card required) or a paid plan.
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.
737
745
 
738
- | Plan | Monitors | Fastest uptime check | Visual pages | Status pages | API keys and MCP account tools |
739
- |---|---|---|---|---|---|
740
- | Solo | 10 | every 5 minutes | 1 site, every 30 minutes | 1 | no |
741
- | Starter | 15 | every 5 minutes | 5, every 15 minutes | 1 | no |
742
- | Business | 60 | every 1 minute | 60, every 15 minutes | 5 | yes |
743
- | Agency | 300 | every 1 minute | 300, every 15 minutes | 50 | yes |
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 |
744
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.
745
754
  - The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.
746
- - The twelve account tools need an API key, which is available on the Business and Agency plans. Create one at https://visualsentinel.com/settings/api-keys.
747
- - Checks run from Europe and the US. Alert channels on every plan: email, Slack, Discord, Microsoft Teams, Telegram, PagerDuty, Opsgenie, webhooks, WhatsApp, Prometheus, and push notifications in the mobile app.
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.
748
758
  `;
749
759
  var HELP = `# Using the Visual Sentinel MCP server
750
760
 
@@ -754,7 +764,7 @@ Visual Sentinel catches pages that look broken even when the site is up. It watc
754
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.
755
765
 
756
766
  ## Connect your account
757
- 1. Get an API key (Business and Agency plans): https://visualsentinel.com/settings/api-keys
767
+ 1. Get an API key (${API_KEY_PLANS_SHORT}): https://visualsentinel.com/settings/api-keys
758
768
  2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.
759
769
  3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.
760
770
 
@@ -770,7 +780,7 @@ check_website_health, investigate_broken_site, pre_launch_checklist and triage_o
770
780
  `;
771
781
  function toolsReference() {
772
782
  const rows = TOOLS.map(
773
- (t) => `| ${t.name} | ${t.requiresAuth ? "API key (Business and Agency)" : "none"} | ${t.annotations.readOnlyHint ? "read-only" : "changes data"} | ${(t.description.split(". ")[0] ?? "").replace(/\.$/, "")} |`
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(/\.$/, "")} |`
774
784
  );
775
785
  return [
776
786
  "# Visual Sentinel tools",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/api-client.ts","../src/tools.ts","../src/prompts.ts","../src/version.ts","../src/resources.ts"],"sourcesContent":["/**\n * Visual Sentinel MCP server (stdio transport).\n *\n * Run via:\n * VS_API_KEY=vs_live_... npx @visualsentinel/mcp-server\n *\n * The server registers all tools defined in `./tools.ts` with the MCP SDK\n * and answers tools/list and tools/call requests. Public tools (health,\n * DNS check, SSL check, speed test, website check) work without an API key;\n * everything else returns a clear error if VS_API_KEY is not set.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ErrorCode,\n ReadResourceRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { ApiClient, VsApiError } from './api-client.js';\nimport { TOOLS, findTool } from './tools.js';\nimport { PROMPTS, findPrompt } from './prompts.js';\nimport { SERVER_VERSION } from './version.js';\nimport { RESOURCES, findResource } from './resources.js';\n\nconst SERVER_NAME = 'visualsentinel';\n\nfunction toMcpToolDescriptor(tool: (typeof TOOLS)[number]) {\n return {\n name: tool.name,\n title: tool.title,\n description: tool.description,\n annotations: tool.annotations,\n inputSchema: tool.inputSchema,\n };\n}\n\nasync function main(): Promise<void> {\n const client = new ApiClient();\n const server = new Server(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: {}, prompts: {}, resources: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOLS.map(toMcpToolDescriptor),\n }));\n\n server.setRequestHandler(ListPromptsRequestSchema, async () => ({\n prompts: PROMPTS.map((p) => ({\n name: p.name,\n title: p.title,\n description: p.description,\n arguments: p.arguments,\n })),\n }));\n\n server.setRequestHandler(GetPromptRequestSchema, async (req) => {\n const prompt = findPrompt(req.params.name);\n if (!prompt) throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${req.params.name}`);\n let text: string;\n try {\n text = prompt.build(req.params.arguments ?? {});\n } catch (err) {\n throw new McpError(ErrorCode.InvalidParams, err instanceof Error ? err.message : String(err));\n }\n return {\n description: prompt.description,\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text } }],\n };\n });\n\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: RESOURCES.map((r) => ({\n uri: r.uri,\n name: r.name,\n title: r.title,\n description: r.description,\n mimeType: r.mimeType,\n })),\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (req) => {\n const resource = findResource(req.params.uri);\n if (!resource) throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${req.params.uri}`);\n return { contents: [{ uri: resource.uri, mimeType: resource.mimeType, text: resource.read() }] };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const tool = findTool(req.params.name);\n if (!tool) {\n return {\n isError: true,\n content: [{ type: 'text', text: `Unknown tool: ${req.params.name}` }],\n };\n }\n\n try {\n const result = await tool.handler(req.params.arguments ?? {}, client);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = err instanceof VsApiError\n ? `Visual Sentinel API error (${err.status}): ${err.message}\\n${\n typeof err.body === 'string' ? err.body : JSON.stringify(err.body, null, 2)\n }`\n : err instanceof Error\n ? err.message\n : String(err);\n return {\n isError: true,\n content: [{ type: 'text', text: message }],\n };\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error('[visualsentinel-mcp-server] fatal:', err);\n process.exit(1);\n});\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\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, which is available on the Business and Agency 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","/**\n * Tool registry for the Visual Sentinel MCP server.\n *\n * Each tool wraps a single Visual Sentinel REST endpoint with a JSON Schema\n * input and a structured handler. Public-tool handlers (DNS check, SSL check,\n * speed test, website check, health) run without authentication; everything\n * else requires `VS_API_KEY`.\n */\n\nimport { ApiClient } from './api-client.js';\n\ninterface ToolInput {\n type: 'object';\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n}\n\nexport interface ToolAnnotations {\n title: string;\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface ToolDefinition {\n name: string;\n /** Human-readable title shown by MCP clients. */\n title: string;\n description: string;\n annotations: ToolAnnotations;\n inputSchema: ToolInput;\n requiresAuth: boolean;\n handler: (args: Record<string, unknown>, client: ApiClient) => Promise<unknown>;\n}\n\nconst STR = { type: 'string' as const };\nconst NUM = { type: 'number' as const };\nconst INT = { type: 'integer' as const };\nconst BOOL = { type: 'boolean' as const };\n\nfunction pickString(args: Record<string, unknown>, key: string): string | undefined {\n const v = args[key];\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction pickNumber(args: Record<string, unknown>, key: string): number | undefined {\n const v = args[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\n\nfunction requireString(args: Record<string, unknown>, key: string): string {\n const v = pickString(args, key);\n if (!v) throw new Error(`Argument \"${key}\" (string) is required.`);\n return v;\n}\n\nexport const TOOLS: ToolDefinition[] = [\n // -------------------------------------------------------------------------\n // Public tools (no auth)\n // -------------------------------------------------------------------------\n {\n name: 'vs_health',\n title: 'Check Visual Sentinel service health',\n annotations: {\n title: 'Check Visual Sentinel service health',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (_args, client) => client.request('GET', '/api/health', { auth: false }),\n },\n\n {\n name: 'vs_dns_check',\n title: 'Look up DNS records',\n annotations: {\n title: 'Look up DNS records',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel\\'s public DNS lookup tool. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Domain to resolve, e.g. example.com (without protocol).' },\n recordType: {\n ...STR,\n description: 'Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all.',\n },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/dns-check', {\n auth: false,\n query: {\n domain: requireString(args, 'domain'),\n // The API reads ?type= (not recordType).\n type: pickString(args, 'recordType'),\n },\n }),\n },\n\n {\n name: 'vs_ssl_check',\n title: 'Inspect an SSL certificate',\n annotations: {\n title: 'Inspect an SSL certificate',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Hostname to inspect, e.g. example.com (without protocol).' },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/ssl-check', {\n auth: false,\n // The API reads ?domain=. `host` stays as an input alias for 0.1.x callers.\n query: { domain: pickString(args, 'domain') ?? requireString(args, 'host') },\n }),\n },\n\n {\n name: 'vs_speed_test',\n title: 'Run a one-shot speed test',\n annotations: {\n title: 'Run a one-shot speed test',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/speed-test', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n {\n name: 'vs_website_check',\n title: 'Check a website',\n annotations: {\n title: 'Check a website',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/website-check', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n // -------------------------------------------------------------------------\n // Authenticated tools (require VS_API_KEY)\n // -------------------------------------------------------------------------\n {\n name: 'vs_monitors_list',\n title: 'List monitors',\n annotations: {\n title: 'List monitors',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'Filter: UP, DOWN, PAUSED, MAINTENANCE.' },\n type: {\n ...STR,\n description:\n 'Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n page: { ...INT, description: 'Page number (default 1).' },\n limit: { ...INT, description: 'Items per page (default 50, max 200).' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/monitors', {\n query: {\n status: pickString(args, 'status'),\n type: pickString(args, 'type'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_get',\n title: 'Get a monitor',\n annotations: {\n title: 'Get a monitor',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch a single monitor by id, including its current status, last check, and configuration.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id (cuid).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_monitors_create',\n title: 'Create a monitor',\n annotations: {\n title: 'Create a monitor',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { ...STR, description: 'Display name for the monitor.' },\n url: { ...STR, description: 'Full URL to monitor (or hostname for PING/PORT/DNS/SSL).' },\n type: {\n ...STR,\n description:\n 'Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n interval: { ...INT, description: 'Check interval in seconds (60, 300, 600, 1800, 3600).' },\n timeout: { ...INT, description: 'Per-check timeout in seconds (default 30).' },\n method: { ...STR, description: 'HTTP method for HTTP/HTTPS monitors (default GET).' },\n keyword: { ...STR, description: 'Keyword to assert for KEYWORD monitors.' },\n port: { ...INT, description: 'TCP port for PORT monitors.' },\n },\n required: ['name', 'url', 'type'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', '/api/monitors', {\n body: {\n name: requireString(args, 'name'),\n url: requireString(args, 'url'),\n type: requireString(args, 'type'),\n interval: pickNumber(args, 'interval'),\n timeout: pickNumber(args, 'timeout'),\n method: pickString(args, 'method'),\n keyword: pickString(args, 'keyword'),\n port: pickNumber(args, 'port'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_check_now',\n title: 'Run a monitor check now',\n annotations: {\n title: 'Run a monitor check now',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/check`),\n },\n\n {\n name: 'vs_monitors_uptime',\n title: 'Get monitor uptime',\n annotations: {\n title: 'Get monitor uptime',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch the uptime percentage and time-series breakdown for a monitor. Use `window` for a preset window (24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n // Precedence mirrors the API: all > from/to > window/period.\n const query = args['all'] === true\n ? { all: true }\n : (from && to)\n ? { from, to }\n : { period: pickString(args, 'window') ?? '30d' };\n return client.request(\n 'GET',\n `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/uptime`,\n { query },\n );\n },\n },\n\n {\n name: 'vs_monitors_metrics_bulk',\n title: 'Get bulk monitor metrics',\n annotations: {\n title: 'Get bulk monitor metrics',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch uptime or response-time metrics for up to 100 monitors in a single request. Use `window` for a preset window (1h, 6h, 24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history. Returns a `monitors` object keyed by monitor id.',\n inputSchema: {\n type: 'object',\n properties: {\n monitorIds: {\n type: 'array',\n items: { type: 'string' },\n description: 'List of monitor IDs to fetch metrics for (max 100).',\n },\n metric: {\n ...STR,\n description: 'Metric to return: \"uptime\" or \"response-time\".',\n },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['monitorIds', 'metric'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const monitorIds = args['monitorIds'];\n if (!Array.isArray(monitorIds) || monitorIds.length === 0) {\n throw new Error('Argument \"monitorIds\" must be a non-empty array.');\n }\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n const body: Record<string, unknown> = {\n monitorIds,\n metric: requireString(args, 'metric'),\n };\n // Precedence mirrors the API: all > from/to > window/period.\n if (args['all'] === true) {\n body['all'] = true;\n } else if (from && to) {\n body['from'] = from;\n body['to'] = to;\n } else {\n body['period'] = pickString(args, 'window') ?? '30d';\n }\n return client.request('POST', '/api/monitors/metrics', { body });\n },\n },\n\n {\n name: 'vs_incidents_list',\n title: 'List incidents',\n annotations: {\n title: 'List incidents',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'OPEN or RESOLVED.' },\n monitorId: { ...STR, description: 'Filter to one monitor.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/incidents', {\n query: {\n status: pickString(args, 'status'),\n monitorId: pickString(args, 'monitorId'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_incidents_get',\n title: 'Get an incident',\n annotations: {\n title: 'Get an incident',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Fetch a single incident with its full check history and root-cause hints.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Incident id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/incidents/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_alerts_list',\n title: 'List alerts',\n annotations: {\n title: 'List alerts',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/alerts', {\n query: {\n status: pickString(args, 'status'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_alerts_acknowledge',\n title: 'Acknowledge an alert',\n annotations: {\n title: 'Acknowledge an alert',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Acknowledge an alert by id. The acknowledgement is recorded against the account that authorised this connection.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Alert id.' },\n note: { ...STR, description: 'Optional acknowledgement note (visible in alert history).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/alerts/${encodeURIComponent(requireString(args, 'id'))}/acknowledge`, {\n body: { note: pickString(args, 'note') },\n }),\n },\n\n {\n name: 'vs_status_pages_list',\n title: 'List status pages',\n annotations: {\n title: 'List status pages',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List public-facing status pages owned by the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (_args, client) => client.request('GET', '/api/status-pages'),\n },\n\n {\n name: 'vs_servers_list',\n title: 'List servers',\n annotations: {\n title: 'List servers',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List servers (origins) registered in the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {\n page: { ...INT },\n limit: { ...INT, description: 'Default 50.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/servers', {\n query: { page: pickNumber(args, 'page'), limit: pickNumber(args, 'limit') },\n }),\n },\n];\n\nexport function findTool(name: string): ToolDefinition | undefined {\n return TOOLS.find((t) => t.name === name);\n}\n\n// Suppress unused-warning suppression: BOOL/NUM are reserved for future tools.\nvoid BOOL;\nvoid NUM;\n","/**\n * MCP prompts: ready-made workflows built on the Visual Sentinel tools.\n * Each prompt returns one user message that tells the assistant which tools to\n * call and how to report back. They work without an API key unless noted.\n */\n\nexport interface PromptArgument {\n name: string;\n description: string;\n required: boolean;\n}\n\nexport interface PromptDefinition {\n name: string;\n title: string;\n description: string;\n arguments: PromptArgument[];\n /** Builds the user message for the given arguments. */\n build: (args: Record<string, string | undefined>) => string;\n}\n\nfunction need(args: Record<string, string | undefined>, key: string): string {\n const v = args[key]?.trim();\n if (!v) throw new Error(`Argument \"${key}\" is required.`);\n return v;\n}\n\nfunction hostOf(value: string): string {\n return value.replace(/^https?:\\/\\//i, '').replace(/\\/.*$/, '');\n}\n\nexport const PROMPTS: PromptDefinition[] = [\n {\n name: 'check_website_health',\n title: 'Check a website\\'s health',\n description:\n 'Run uptime, SSL, DNS and speed checks on one site and summarise what needs attention. No account or API key needed.',\n arguments: [\n { name: 'url', description: 'Site to check, e.g. https://example.com', required: true },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n return [\n `Check the health of ${url} with the Visual Sentinel tools and report back in plain language.`,\n '',\n `1. vs_website_check with url \"${url}\": HTTP status, response time, server header.`,\n `2. vs_ssl_check with domain \"${host}\": certificate validity, expiry date and chain.`,\n `3. vs_dns_check with domain \"${host}\": A, AAAA, MX, NS and TXT records.`,\n `4. vs_speed_test with url \"${url}\": time to first byte, total load time, transfer size, redirects.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'investigate_broken_site',\n title: 'Investigate why my site looks broken',\n description:\n '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.',\n arguments: [\n { name: 'url', description: 'The affected page, e.g. https://example.com/pricing', required: true },\n { name: 'symptom', description: 'What you see, e.g. \"blank page\", \"images missing\", \"very slow\"', required: false },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n const symptom = args.symptom?.trim();\n return [\n `${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:`,\n '',\n `1. DNS: vs_dns_check for \"${host}\". Do the records resolve, and do they point where they should?`,\n `2. TLS: vs_ssl_check for \"${host}\". Is the certificate valid, unexpired and issued for this hostname?`,\n `3. Server response: vs_website_check for \"${url}\". Status code, response time, content snippet.`,\n `4. Speed: vs_speed_test for \"${url}\". Is time to first byte or load time unusually high, and is there a redirect chain?`,\n '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.',\n '',\n '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.',\n 'Report: the evidence from each step, the most likely cause, and the next action.',\n ].join('\\n');\n },\n },\n {\n name: 'pre_launch_checklist',\n title: 'Pre-launch checklist for a site',\n description:\n '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.',\n arguments: [\n { name: 'domain', description: 'The domain being launched, e.g. example.com', required: true },\n ],\n build: (args) => {\n const domain = hostOf(need(args, 'domain'));\n return [\n `Run a pre-launch check on ${domain} with the Visual Sentinel tools and return a checklist with Pass, Warn or Fail for each line.`,\n '',\n `- 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.`,\n `- Certificate: vs_ssl_check for \"${domain}\" and for \"www.${domain}\". Valid, issued for the hostname, and more than 30 days from expiry.`,\n `- Response: vs_website_check for \"https://${domain}\" and \"https://www.${domain}\". Both return 200 or one cleanly redirects to the other.`,\n `- Speed: vs_speed_test for \"https://${domain}\". Report time to first byte, total load time and redirect count.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'triage_open_incidents',\n title: 'Triage open incidents',\n description:\n 'Review open incidents and unacknowledged alerts on your account and rank what to look at first. Needs a Visual Sentinel API key (Business and Agency plans).',\n arguments: [],\n build: () =>\n [\n 'Review my Visual Sentinel account and tell me what needs attention first.',\n '',\n '1. vs_incidents_list with status OPEN, then vs_incidents_get for each one (up to 5, most recent first).',\n '2. vs_alerts_list with status UNACKNOWLEDGED.',\n '3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.',\n '',\n '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 from the Business or Agency plan, and stop. Ask before acknowledging any alert.',\n ].join('\\n'),\n },\n];\n\nexport function findPrompt(name: string): PromptDefinition | undefined {\n return PROMPTS.find((p) => p.name === name);\n}\n","/** Keep in sync with package.json (a test checks it). */\nexport const SERVER_VERSION = '0.2.2';\n","/**\n * MCP resources: read-only reference material the assistant can attach as\n * context. Static on purpose, so they work without an API key and never touch\n * account data. Plan limits come from the public pricing page; check\n * https://visualsentinel.com/pricing for current prices.\n */\n\nimport { TOOLS } from './tools.js';\n\nexport interface ResourceDefinition {\n uri: string;\n name: string;\n title: string;\n description: string;\n mimeType: string;\n read: () => string;\n}\n\nconst PLANS_AS_OF = '2026-10-16';\n\nconst PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})\n\nPrices and the current list are always at https://visualsentinel.com/pricing. There is no free plan for new accounts: a new workspace gets a first check of its site, then a 7-day trial (card required) or a paid plan.\n\n| Plan | Monitors | Fastest uptime check | Visual pages | Status pages | API keys and MCP account tools |\n|---|---|---|---|---|---|\n| Solo | 10 | every 5 minutes | 1 site, every 30 minutes | 1 | no |\n| Starter | 15 | every 5 minutes | 5, every 15 minutes | 1 | no |\n| Business | 60 | every 1 minute | 60, every 15 minutes | 5 | yes |\n| Agency | 300 | every 1 minute | 300, every 15 minutes | 50 | yes |\n\n- The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.\n- The twelve account tools need an API key, which is available on the Business and Agency plans. Create one at https://visualsentinel.com/settings/api-keys.\n- Checks run from Europe and the US. Alert channels on every plan: email, Slack, Discord, Microsoft Teams, Telegram, PagerDuty, Opsgenie, webhooks, WhatsApp, Prometheus, and push notifications in the mobile app.\n`;\n\nconst HELP = `# Using the Visual Sentinel MCP server\n\nVisual Sentinel catches pages that look broken even when the site is up. It watches uptime, performance, SSL certificates, DNS, visual changes and content changes.\n\n## Start without an account\nAsk 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.\n\n## Connect your account\n1. Get an API key (Business and Agency plans): https://visualsentinel.com/settings/api-keys\n2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.\n3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.\n\n## Ready-made prompts\ncheck_website_health, investigate_broken_site, pre_launch_checklist and triage_open_incidents.\n\n## Links\n- Docs and install: https://visualsentinel.com/mcp\n- API reference: https://visualsentinel.com/docs\n- Pricing: https://visualsentinel.com/pricing\n- Source: https://github.com/VisualSentinel/mcp-server\n- Support: https://visualsentinel.com/contact\n`;\n\nfunction toolsReference(): string {\n const rows = TOOLS.map(\n (t) => `| ${t.name} | ${t.requiresAuth ? 'API key (Business and Agency)' : 'none'} | ${t.annotations.readOnlyHint ? 'read-only' : 'changes data'} | ${(t.description.split('. ')[0] ?? '').replace(/\\.$/, '')} |`,\n );\n return [\n '# Visual Sentinel tools',\n '',\n '| Tool | Needs | Type | What it does |',\n '|---|---|---|---|',\n ...rows,\n '',\n ].join('\\n');\n}\n\nexport const RESOURCES: ResourceDefinition[] = [\n {\n uri: 'visualsentinel://plans',\n name: 'plans',\n title: 'Plans and limits',\n description: 'Monitor counts, check frequencies and which plans include API keys and the MCP account tools.',\n mimeType: 'text/markdown',\n read: () => PLANS,\n },\n {\n uri: 'visualsentinel://help',\n name: 'help',\n title: 'Help and setup',\n description: 'How to use the server without an account, how to add an API key, and where the docs are.',\n mimeType: 'text/markdown',\n read: () => HELP,\n },\n {\n uri: 'visualsentinel://tools',\n name: 'tools',\n title: 'Tool reference',\n description: 'Every tool, whether it needs an API key, and whether it is read-only.',\n mimeType: 'text/markdown',\n read: toolsReference,\n },\n];\n\nexport function findResource(uri: string): ResourceDefinition | undefined {\n return RESOURCES.find((r) => r.uri === uri);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/plan-copy.ts","../src/api-client.ts","../src/tools.ts","../src/prompts.ts","../src/version.ts","../src/resources.ts"],"sourcesContent":["/**\n * Visual Sentinel MCP server (stdio transport).\n *\n * Run via:\n * VS_API_KEY=vs_live_... npx @visualsentinel/mcp-server\n *\n * The server registers all tools defined in `./tools.ts` with the MCP SDK\n * and answers tools/list and tools/call requests. Public tools (health,\n * DNS check, SSL check, speed test, website check) work without an API key;\n * everything else returns a clear error if VS_API_KEY is not set.\n */\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport {\n CallToolRequestSchema,\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ErrorCode,\n ReadResourceRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { ApiClient, VsApiError } from './api-client.js';\nimport { TOOLS, findTool } from './tools.js';\nimport { PROMPTS, findPrompt } from './prompts.js';\nimport { SERVER_VERSION } from './version.js';\nimport { RESOURCES, findResource } from './resources.js';\n\nconst SERVER_NAME = 'visualsentinel';\n\nfunction toMcpToolDescriptor(tool: (typeof TOOLS)[number]) {\n return {\n name: tool.name,\n title: tool.title,\n description: tool.description,\n annotations: tool.annotations,\n inputSchema: tool.inputSchema,\n };\n}\n\nasync function main(): Promise<void> {\n const client = new ApiClient();\n const server = new Server(\n { name: SERVER_NAME, version: SERVER_VERSION },\n { capabilities: { tools: {}, prompts: {}, resources: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOLS.map(toMcpToolDescriptor),\n }));\n\n server.setRequestHandler(ListPromptsRequestSchema, async () => ({\n prompts: PROMPTS.map((p) => ({\n name: p.name,\n title: p.title,\n description: p.description,\n arguments: p.arguments,\n })),\n }));\n\n server.setRequestHandler(GetPromptRequestSchema, async (req) => {\n const prompt = findPrompt(req.params.name);\n if (!prompt) throw new McpError(ErrorCode.InvalidParams, `Unknown prompt: ${req.params.name}`);\n let text: string;\n try {\n text = prompt.build(req.params.arguments ?? {});\n } catch (err) {\n throw new McpError(ErrorCode.InvalidParams, err instanceof Error ? err.message : String(err));\n }\n return {\n description: prompt.description,\n messages: [{ role: 'user' as const, content: { type: 'text' as const, text } }],\n };\n });\n\n server.setRequestHandler(ListResourcesRequestSchema, async () => ({\n resources: RESOURCES.map((r) => ({\n uri: r.uri,\n name: r.name,\n title: r.title,\n description: r.description,\n mimeType: r.mimeType,\n })),\n }));\n\n server.setRequestHandler(ReadResourceRequestSchema, async (req) => {\n const resource = findResource(req.params.uri);\n if (!resource) throw new McpError(ErrorCode.InvalidParams, `Unknown resource: ${req.params.uri}`);\n return { contents: [{ uri: resource.uri, mimeType: resource.mimeType, text: resource.read() }] };\n });\n\n server.setRequestHandler(CallToolRequestSchema, async (req) => {\n const tool = findTool(req.params.name);\n if (!tool) {\n return {\n isError: true,\n content: [{ type: 'text', text: `Unknown tool: ${req.params.name}` }],\n };\n }\n\n try {\n const result = await tool.handler(req.params.arguments ?? {}, client);\n return {\n content: [\n {\n type: 'text',\n text: JSON.stringify(result, null, 2),\n },\n ],\n };\n } catch (err) {\n const message = err instanceof VsApiError\n ? `Visual Sentinel API error (${err.status}): ${err.message}\\n${\n typeof err.body === 'string' ? err.body : JSON.stringify(err.body, null, 2)\n }`\n : err instanceof Error\n ? err.message\n : String(err);\n return {\n isError: true,\n content: [{ type: 'text', text: message }],\n };\n }\n });\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nmain().catch((err) => {\n // eslint-disable-next-line no-console\n console.error('[visualsentinel-mcp-server] fatal:', err);\n process.exit(1);\n});\n","/**\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","/**\n * Tool registry for the Visual Sentinel MCP server.\n *\n * Each tool wraps a single Visual Sentinel REST endpoint with a JSON Schema\n * input and a structured handler. Public-tool handlers (DNS check, SSL check,\n * speed test, website check, health) run without authentication; everything\n * else requires `VS_API_KEY`.\n */\n\nimport { ApiClient } from './api-client.js';\n\ninterface ToolInput {\n type: 'object';\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n}\n\nexport interface ToolAnnotations {\n title: string;\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface ToolDefinition {\n name: string;\n /** Human-readable title shown by MCP clients. */\n title: string;\n description: string;\n annotations: ToolAnnotations;\n inputSchema: ToolInput;\n requiresAuth: boolean;\n handler: (args: Record<string, unknown>, client: ApiClient) => Promise<unknown>;\n}\n\nconst STR = { type: 'string' as const };\nconst NUM = { type: 'number' as const };\nconst INT = { type: 'integer' as const };\nconst BOOL = { type: 'boolean' as const };\n\nfunction pickString(args: Record<string, unknown>, key: string): string | undefined {\n const v = args[key];\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction pickNumber(args: Record<string, unknown>, key: string): number | undefined {\n const v = args[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\n\nfunction requireString(args: Record<string, unknown>, key: string): string {\n const v = pickString(args, key);\n if (!v) throw new Error(`Argument \"${key}\" (string) is required.`);\n return v;\n}\n\nexport const TOOLS: ToolDefinition[] = [\n // -------------------------------------------------------------------------\n // Public tools (no auth)\n // -------------------------------------------------------------------------\n {\n name: 'vs_health',\n title: 'Check Visual Sentinel service health',\n annotations: {\n title: 'Check Visual Sentinel service health',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (_args, client) => client.request('GET', '/api/health', { auth: false }),\n },\n\n {\n name: 'vs_dns_check',\n title: 'Look up DNS records',\n annotations: {\n title: 'Look up DNS records',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel\\'s public DNS lookup tool. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Domain to resolve, e.g. example.com (without protocol).' },\n recordType: {\n ...STR,\n description: 'Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all.',\n },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/dns-check', {\n auth: false,\n query: {\n domain: requireString(args, 'domain'),\n // The API reads ?type= (not recordType).\n type: pickString(args, 'recordType'),\n },\n }),\n },\n\n {\n name: 'vs_ssl_check',\n title: 'Inspect an SSL certificate',\n annotations: {\n title: 'Inspect an SSL certificate',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Hostname to inspect, e.g. example.com (without protocol).' },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/ssl-check', {\n auth: false,\n // The API reads ?domain=. `host` stays as an input alias for 0.1.x callers.\n query: { domain: pickString(args, 'domain') ?? requireString(args, 'host') },\n }),\n },\n\n {\n name: 'vs_speed_test',\n title: 'Run a one-shot speed test',\n annotations: {\n title: 'Run a one-shot speed test',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/speed-test', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n {\n name: 'vs_website_check',\n title: 'Check a website',\n annotations: {\n title: 'Check a website',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/website-check', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n // -------------------------------------------------------------------------\n // Authenticated tools (require VS_API_KEY)\n // -------------------------------------------------------------------------\n {\n name: 'vs_monitors_list',\n title: 'List monitors',\n annotations: {\n title: 'List monitors',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'Filter: UP, DOWN, PAUSED, MAINTENANCE.' },\n type: {\n ...STR,\n description:\n 'Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n page: { ...INT, description: 'Page number (default 1).' },\n limit: { ...INT, description: 'Items per page (default 50, max 200).' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/monitors', {\n query: {\n status: pickString(args, 'status'),\n type: pickString(args, 'type'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_get',\n title: 'Get a monitor',\n annotations: {\n title: 'Get a monitor',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch a single monitor by id, including its current status, last check, and configuration.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id (cuid).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_monitors_create',\n title: 'Create a monitor',\n annotations: {\n title: 'Create a monitor',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { ...STR, description: 'Display name for the monitor.' },\n url: { ...STR, description: 'Full URL to monitor (or hostname for PING/PORT/DNS/SSL).' },\n type: {\n ...STR,\n description:\n 'Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n interval: { ...INT, description: 'Check interval in seconds (60, 300, 600, 1800, 3600).' },\n timeout: { ...INT, description: 'Per-check timeout in seconds (default 30).' },\n method: { ...STR, description: 'HTTP method for HTTP/HTTPS monitors (default GET).' },\n keyword: { ...STR, description: 'Keyword to assert for KEYWORD monitors.' },\n port: { ...INT, description: 'TCP port for PORT monitors.' },\n },\n required: ['name', 'url', 'type'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', '/api/monitors', {\n body: {\n name: requireString(args, 'name'),\n url: requireString(args, 'url'),\n type: requireString(args, 'type'),\n interval: pickNumber(args, 'interval'),\n timeout: pickNumber(args, 'timeout'),\n method: pickString(args, 'method'),\n keyword: pickString(args, 'keyword'),\n port: pickNumber(args, 'port'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_check_now',\n title: 'Run a monitor check now',\n annotations: {\n title: 'Run a monitor check now',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/check`),\n },\n\n {\n name: 'vs_monitors_uptime',\n title: 'Get monitor uptime',\n annotations: {\n title: 'Get monitor uptime',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch the uptime percentage and time-series breakdown for a monitor. Use `window` for a preset window (24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n // Precedence mirrors the API: all > from/to > window/period.\n const query = args['all'] === true\n ? { all: true }\n : (from && to)\n ? { from, to }\n : { period: pickString(args, 'window') ?? '30d' };\n return client.request(\n 'GET',\n `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/uptime`,\n { query },\n );\n },\n },\n\n {\n name: 'vs_monitors_metrics_bulk',\n title: 'Get bulk monitor metrics',\n annotations: {\n title: 'Get bulk monitor metrics',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch uptime or response-time metrics for up to 100 monitors in a single request. Use `window` for a preset window (1h, 6h, 24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history. Returns a `monitors` object keyed by monitor id.',\n inputSchema: {\n type: 'object',\n properties: {\n monitorIds: {\n type: 'array',\n items: { type: 'string' },\n description: 'List of monitor IDs to fetch metrics for (max 100).',\n },\n metric: {\n ...STR,\n description: 'Metric to return: \"uptime\" or \"response-time\".',\n },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['monitorIds', 'metric'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const monitorIds = args['monitorIds'];\n if (!Array.isArray(monitorIds) || monitorIds.length === 0) {\n throw new Error('Argument \"monitorIds\" must be a non-empty array.');\n }\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n const body: Record<string, unknown> = {\n monitorIds,\n metric: requireString(args, 'metric'),\n };\n // Precedence mirrors the API: all > from/to > window/period.\n if (args['all'] === true) {\n body['all'] = true;\n } else if (from && to) {\n body['from'] = from;\n body['to'] = to;\n } else {\n body['period'] = pickString(args, 'window') ?? '30d';\n }\n return client.request('POST', '/api/monitors/metrics', { body });\n },\n },\n\n {\n name: 'vs_incidents_list',\n title: 'List incidents',\n annotations: {\n title: 'List incidents',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'OPEN or RESOLVED.' },\n monitorId: { ...STR, description: 'Filter to one monitor.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/incidents', {\n query: {\n status: pickString(args, 'status'),\n monitorId: pickString(args, 'monitorId'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_incidents_get',\n title: 'Get an incident',\n annotations: {\n title: 'Get an incident',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Fetch a single incident with its full check history and root-cause hints.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Incident id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/incidents/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_alerts_list',\n title: 'List alerts',\n annotations: {\n title: 'List alerts',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/alerts', {\n query: {\n status: pickString(args, 'status'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_alerts_acknowledge',\n title: 'Acknowledge an alert',\n annotations: {\n title: 'Acknowledge an alert',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Acknowledge an alert by id. The acknowledgement is recorded against the account that authorised this connection.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Alert id.' },\n note: { ...STR, description: 'Optional acknowledgement note (visible in alert history).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/alerts/${encodeURIComponent(requireString(args, 'id'))}/acknowledge`, {\n body: { note: pickString(args, 'note') },\n }),\n },\n\n {\n name: 'vs_status_pages_list',\n title: 'List status pages',\n annotations: {\n title: 'List status pages',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List public-facing status pages owned by the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (_args, client) => client.request('GET', '/api/status-pages'),\n },\n\n {\n name: 'vs_servers_list',\n title: 'List servers',\n annotations: {\n title: 'List servers',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List servers (origins) registered in the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {\n page: { ...INT },\n limit: { ...INT, description: 'Default 50.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/servers', {\n query: { page: pickNumber(args, 'page'), limit: pickNumber(args, 'limit') },\n }),\n },\n];\n\nexport function findTool(name: string): ToolDefinition | undefined {\n return TOOLS.find((t) => t.name === name);\n}\n\n// Suppress unused-warning suppression: BOOL/NUM are reserved for future tools.\nvoid BOOL;\nvoid NUM;\n","/**\n * MCP prompts: ready-made workflows built on the Visual Sentinel tools.\n * Each prompt returns one user message that tells the assistant which tools to\n * call and how to report back. They work without an API key unless noted.\n */\n\nimport { API_KEY_PLANS, API_KEY_PLANS_SHORT } from './plan-copy.js';\n\nexport interface PromptArgument {\n name: string;\n description: string;\n required: boolean;\n}\n\nexport interface PromptDefinition {\n name: string;\n title: string;\n description: string;\n arguments: PromptArgument[];\n /** Builds the user message for the given arguments. */\n build: (args: Record<string, string | undefined>) => string;\n}\n\nfunction need(args: Record<string, string | undefined>, key: string): string {\n const v = args[key]?.trim();\n if (!v) throw new Error(`Argument \"${key}\" is required.`);\n return v;\n}\n\nfunction hostOf(value: string): string {\n return value.replace(/^https?:\\/\\//i, '').replace(/\\/.*$/, '');\n}\n\nexport const PROMPTS: PromptDefinition[] = [\n {\n name: 'check_website_health',\n title: 'Check a website\\'s health',\n description:\n 'Run uptime, SSL, DNS and speed checks on one site and summarise what needs attention. No account or API key needed.',\n arguments: [\n { name: 'url', description: 'Site to check, e.g. https://example.com', required: true },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n return [\n `Check the health of ${url} with the Visual Sentinel tools and report back in plain language.`,\n '',\n `1. vs_website_check with url \"${url}\": HTTP status, response time, server header.`,\n `2. vs_ssl_check with domain \"${host}\": certificate validity, expiry date and chain.`,\n `3. vs_dns_check with domain \"${host}\": A, AAAA, MX, NS and TXT records.`,\n `4. vs_speed_test with url \"${url}\": time to first byte, total load time, transfer size, redirects.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'investigate_broken_site',\n title: 'Investigate why my site looks broken',\n description:\n '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.',\n arguments: [\n { name: 'url', description: 'The affected page, e.g. https://example.com/pricing', required: true },\n { name: 'symptom', description: 'What you see, e.g. \"blank page\", \"images missing\", \"very slow\"', required: false },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n const symptom = args.symptom?.trim();\n return [\n `${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:`,\n '',\n `1. DNS: vs_dns_check for \"${host}\". Do the records resolve, and do they point where they should?`,\n `2. TLS: vs_ssl_check for \"${host}\". Is the certificate valid, unexpired and issued for this hostname?`,\n `3. Server response: vs_website_check for \"${url}\". Status code, response time, content snippet.`,\n `4. Speed: vs_speed_test for \"${url}\". Is time to first byte or load time unusually high, and is there a redirect chain?`,\n '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.',\n '',\n '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.',\n 'Report: the evidence from each step, the most likely cause, and the next action.',\n ].join('\\n');\n },\n },\n {\n name: 'pre_launch_checklist',\n title: 'Pre-launch checklist for a site',\n description:\n '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.',\n arguments: [\n { name: 'domain', description: 'The domain being launched, e.g. example.com', required: true },\n ],\n build: (args) => {\n const domain = hostOf(need(args, 'domain'));\n return [\n `Run a pre-launch check on ${domain} with the Visual Sentinel tools and return a checklist with Pass, Warn or Fail for each line.`,\n '',\n `- 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.`,\n `- Certificate: vs_ssl_check for \"${domain}\" and for \"www.${domain}\". Valid, issued for the hostname, and more than 30 days from expiry.`,\n `- Response: vs_website_check for \"https://${domain}\" and \"https://www.${domain}\". Both return 200 or one cleanly redirects to the other.`,\n `- Speed: vs_speed_test for \"https://${domain}\". Report time to first byte, total load time and redirect count.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'triage_open_incidents',\n title: 'Triage open incidents',\n description:\n `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}).`,\n arguments: [],\n build: () =>\n [\n 'Review my Visual Sentinel account and tell me what needs attention first.',\n '',\n '1. vs_incidents_list with status OPEN, then vs_incidents_get for each one (up to 5, most recent first).',\n '2. vs_alerts_list with status UNACKNOWLEDGED.',\n '3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.',\n '',\n `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.`,\n ].join('\\n'),\n },\n];\n\nexport function findPrompt(name: string): PromptDefinition | undefined {\n return PROMPTS.find((p) => p.name === name);\n}\n","/** Keep in sync with package.json (a test checks it). */\nexport const SERVER_VERSION = '0.2.3';\n","/**\n * MCP resources: read-only reference material the assistant can attach as\n * context. Static on purpose, so they work without an API key and never touch\n * account data. Plan prices and limits mirror the public pricing page as of\n * PLANS_AS_OF; https://visualsentinel.com/pricing is always current.\n */\n\nimport { TOOLS } from './tools.js';\nimport { API_KEY_PLANS, API_KEY_PLANS_SHORT, MONEY_BACK_SENTENCE, NO_FREE_PLAN_SENTENCE } from './plan-copy.js';\n\nexport interface ResourceDefinition {\n uri: string;\n name: string;\n title: string;\n description: string;\n mimeType: string;\n read: () => string;\n}\n\nconst PLANS_AS_OF = '2026-11-09';\n\nconst PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})\n\nPrices 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.\n\nPrices are in US dollars before tax. Yearly costs 10 months of monthly.\n\n| Plan | Price | Monitors | Fastest uptime check | Sites with screenshot checks | Status pages | API keys and MCP account tools |\n|---|---|---|---|---|---|---|\n| Solo | $15 a month or $150 a year | 10 | every 5 minutes | 1 site, up to 4 pages, every 30 minutes | 1 | yes |\n| Starter | $30 a month or $300 a year | 15 | every 5 minutes | 5 sites, up to 6 pages each, every 15 minutes | 1 | yes |\n| Business | $80 a month or $800 a year | 60 | every minute | 60 sites, up to 6 pages each, every 15 minutes | 5 | yes |\n| Agency | $300 a month or $3,000 a year | 300 | every minute | 300 sites, up to 6 pages each, every 15 minutes | 50 | yes |\n\n- 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.\n- The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.\n- The twelve account tools need an API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys.\n- 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.\n- 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.\n`;\n\nconst HELP = `# Using the Visual Sentinel MCP server\n\nVisual Sentinel catches pages that look broken even when the site is up. It watches uptime, performance, SSL certificates, DNS, visual changes and content changes.\n\n## Start without an account\nAsk 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.\n\n## Connect your account\n1. Get an API key (${API_KEY_PLANS_SHORT}): https://visualsentinel.com/settings/api-keys\n2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.\n3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.\n\n## Ready-made prompts\ncheck_website_health, investigate_broken_site, pre_launch_checklist and triage_open_incidents.\n\n## Links\n- Docs and install: https://visualsentinel.com/mcp\n- API reference: https://visualsentinel.com/docs\n- Pricing: https://visualsentinel.com/pricing\n- Source: https://github.com/VisualSentinel/mcp-server\n- Support: https://visualsentinel.com/contact\n`;\n\nfunction toolsReference(): string {\n const rows = TOOLS.map(\n (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(/\\.$/, '')} |`,\n );\n return [\n '# Visual Sentinel tools',\n '',\n '| Tool | Needs | Type | What it does |',\n '|---|---|---|---|',\n ...rows,\n '',\n ].join('\\n');\n}\n\nexport const RESOURCES: ResourceDefinition[] = [\n {\n uri: 'visualsentinel://plans',\n name: 'plans',\n title: 'Plans and limits',\n description: 'Monitor counts, check frequencies and which plans include API keys and the MCP account tools.',\n mimeType: 'text/markdown',\n read: () => PLANS,\n },\n {\n uri: 'visualsentinel://help',\n name: 'help',\n title: 'Help and setup',\n description: 'How to use the server without an account, how to add an API key, and where the docs are.',\n mimeType: 'text/markdown',\n read: () => HELP,\n },\n {\n uri: 'visualsentinel://tools',\n name: 'tools',\n title: 'Tool reference',\n description: 'Every tool, whether it needs an API key, and whether it is read-only.',\n mimeType: 'text/markdown',\n read: toolsReference,\n },\n];\n\nexport function findResource(uri: string): ResourceDefinition | undefined {\n return RESOURCES.find((r) => r.uri === uri);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Plan wording shared by the resources, the prompts and the no-key error, so
3
+ * the plan statement lives in one place. It mirrors the Visual Sentinel offer
4
+ * as of PLANS_AS_OF in resources.ts; https://visualsentinel.com/pricing is
5
+ * always current.
6
+ *
7
+ * API keys (and so the account tools) come with every paid plan. A trial of a
8
+ * paid plan gets that plan's API access, with or without a card. A workspace
9
+ * on the older Free plan, one that has not started a trial or subscription,
10
+ * and one whose monitoring is paused cannot use its keys.
11
+ */
12
+ /** Finishes "API keys come with ...". */
13
+ declare const API_KEY_PLANS = "every paid plan (Solo, Starter, Business and Agency), including during the 10-day free trial";
14
+ /** The same, short, for parentheses and table cells. */
15
+ declare const API_KEY_PLANS_SHORT = "every paid plan, including the 10-day free trial";
16
+ declare const NO_FREE_PLAN_SENTENCE = "Sign-ups on the website have no Free plan.";
17
+ declare const MONEY_BACK_SENTENCE = "Every paid plan has a 30-day money-back guarantee from the first payment.";
18
+
19
+ export { API_KEY_PLANS, API_KEY_PLANS_SHORT, MONEY_BACK_SENTENCE, NO_FREE_PLAN_SENTENCE };
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
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
+ var API_KEY_PLANS_SHORT = "every paid plan, including the 10-day free trial";
6
+ var NO_FREE_PLAN_SENTENCE = "Sign-ups on the website have no Free plan.";
7
+ var MONEY_BACK_SENTENCE = "Every paid plan has a 30-day money-back guarantee from the first payment.";
8
+ export {
9
+ API_KEY_PLANS,
10
+ API_KEY_PLANS_SHORT,
11
+ MONEY_BACK_SENTENCE,
12
+ NO_FREE_PLAN_SENTENCE
13
+ };
14
+ //# sourceMappingURL=plan-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/plan-copy.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"],"mappings":";;;AAaO,IAAM,gBACX;AAGK,IAAM,sBAAsB;AAE5B,IAAM,wBAAwB;AAE9B,IAAM,sBAAsB;","names":[]}
package/dist/prompts.js CHANGED
@@ -1,5 +1,9 @@
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
+ var API_KEY_PLANS_SHORT = "every paid plan, including the 10-day free trial";
6
+
3
7
  // src/prompts.ts
4
8
  function need(args, key) {
5
9
  const v = args[key]?.trim();
@@ -82,7 +86,7 @@ var PROMPTS = [
82
86
  {
83
87
  name: "triage_open_incidents",
84
88
  title: "Triage open incidents",
85
- description: "Review open incidents and unacknowledged alerts on your account and rank what to look at first. Needs a Visual Sentinel API key (Business and Agency plans).",
89
+ 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}).`,
86
90
  arguments: [],
87
91
  build: () => [
88
92
  "Review my Visual Sentinel account and tell me what needs attention first.",
@@ -91,7 +95,7 @@ var PROMPTS = [
91
95
  "2. vs_alerts_list with status UNACKNOWLEDGED.",
92
96
  "3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.",
93
97
  "",
94
- '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 from the Business or Agency plan, and stop. Ask before acknowledging any alert.'
98
+ `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.`
95
99
  ].join("\n")
96
100
  }
97
101
  ];
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["/**\n * MCP prompts: ready-made workflows built on the Visual Sentinel tools.\n * Each prompt returns one user message that tells the assistant which tools to\n * call and how to report back. They work without an API key unless noted.\n */\n\nexport interface PromptArgument {\n name: string;\n description: string;\n required: boolean;\n}\n\nexport interface PromptDefinition {\n name: string;\n title: string;\n description: string;\n arguments: PromptArgument[];\n /** Builds the user message for the given arguments. */\n build: (args: Record<string, string | undefined>) => string;\n}\n\nfunction need(args: Record<string, string | undefined>, key: string): string {\n const v = args[key]?.trim();\n if (!v) throw new Error(`Argument \"${key}\" is required.`);\n return v;\n}\n\nfunction hostOf(value: string): string {\n return value.replace(/^https?:\\/\\//i, '').replace(/\\/.*$/, '');\n}\n\nexport const PROMPTS: PromptDefinition[] = [\n {\n name: 'check_website_health',\n title: 'Check a website\\'s health',\n description:\n 'Run uptime, SSL, DNS and speed checks on one site and summarise what needs attention. No account or API key needed.',\n arguments: [\n { name: 'url', description: 'Site to check, e.g. https://example.com', required: true },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n return [\n `Check the health of ${url} with the Visual Sentinel tools and report back in plain language.`,\n '',\n `1. vs_website_check with url \"${url}\": HTTP status, response time, server header.`,\n `2. vs_ssl_check with domain \"${host}\": certificate validity, expiry date and chain.`,\n `3. vs_dns_check with domain \"${host}\": A, AAAA, MX, NS and TXT records.`,\n `4. vs_speed_test with url \"${url}\": time to first byte, total load time, transfer size, redirects.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'investigate_broken_site',\n title: 'Investigate why my site looks broken',\n description:\n '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.',\n arguments: [\n { name: 'url', description: 'The affected page, e.g. https://example.com/pricing', required: true },\n { name: 'symptom', description: 'What you see, e.g. \"blank page\", \"images missing\", \"very slow\"', required: false },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n const symptom = args.symptom?.trim();\n return [\n `${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:`,\n '',\n `1. DNS: vs_dns_check for \"${host}\". Do the records resolve, and do they point where they should?`,\n `2. TLS: vs_ssl_check for \"${host}\". Is the certificate valid, unexpired and issued for this hostname?`,\n `3. Server response: vs_website_check for \"${url}\". Status code, response time, content snippet.`,\n `4. Speed: vs_speed_test for \"${url}\". Is time to first byte or load time unusually high, and is there a redirect chain?`,\n '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.',\n '',\n '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.',\n 'Report: the evidence from each step, the most likely cause, and the next action.',\n ].join('\\n');\n },\n },\n {\n name: 'pre_launch_checklist',\n title: 'Pre-launch checklist for a site',\n description:\n '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.',\n arguments: [\n { name: 'domain', description: 'The domain being launched, e.g. example.com', required: true },\n ],\n build: (args) => {\n const domain = hostOf(need(args, 'domain'));\n return [\n `Run a pre-launch check on ${domain} with the Visual Sentinel tools and return a checklist with Pass, Warn or Fail for each line.`,\n '',\n `- 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.`,\n `- Certificate: vs_ssl_check for \"${domain}\" and for \"www.${domain}\". Valid, issued for the hostname, and more than 30 days from expiry.`,\n `- Response: vs_website_check for \"https://${domain}\" and \"https://www.${domain}\". Both return 200 or one cleanly redirects to the other.`,\n `- Speed: vs_speed_test for \"https://${domain}\". Report time to first byte, total load time and redirect count.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'triage_open_incidents',\n title: 'Triage open incidents',\n description:\n 'Review open incidents and unacknowledged alerts on your account and rank what to look at first. Needs a Visual Sentinel API key (Business and Agency plans).',\n arguments: [],\n build: () =>\n [\n 'Review my Visual Sentinel account and tell me what needs attention first.',\n '',\n '1. vs_incidents_list with status OPEN, then vs_incidents_get for each one (up to 5, most recent first).',\n '2. vs_alerts_list with status UNACKNOWLEDGED.',\n '3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.',\n '',\n '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 from the Business or Agency plan, and stop. Ask before acknowledging any alert.',\n ].join('\\n'),\n },\n];\n\nexport function findPrompt(name: string): PromptDefinition | undefined {\n return PROMPTS.find((p) => p.name === name);\n}\n"],"mappings":";;;AAqBA,SAAS,KAAK,MAA0C,KAAqB;AAC3E,QAAM,IAAI,KAAK,GAAG,GAAG,KAAK;AAC1B,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,aAAa,GAAG,gBAAgB;AACxD,SAAO;AACT;AAEA,SAAS,OAAO,OAAuB;AACrC,SAAO,MAAM,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,SAAS,EAAE;AAC/D;AAEO,IAAM,UAA8B;AAAA,EACzC;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW;AAAA,MACT,EAAE,MAAM,OAAO,aAAa,2CAA2C,UAAU,KAAK;AAAA,IACxF;AAAA,IACA,OAAO,CAAC,SAAS;AACf,YAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,YAAM,OAAO,OAAO,GAAG;AACvB,aAAO;AAAA,QACL,uBAAuB,GAAG;AAAA,QAC1B;AAAA,QACA,iCAAiC,GAAG;AAAA,QACpC,gCAAgC,IAAI;AAAA,QACpC,gCAAgC,IAAI;AAAA,QACpC,8BAA8B,GAAG;AAAA,QACjC;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW;AAAA,MACT,EAAE,MAAM,OAAO,aAAa,uDAAuD,UAAU,KAAK;AAAA,MAClG,EAAE,MAAM,WAAW,aAAa,kEAAkE,UAAU,MAAM;AAAA,IACpH;AAAA,IACA,OAAO,CAAC,SAAS;AACf,YAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,YAAM,OAAO,OAAO,GAAG;AACvB,YAAM,UAAU,KAAK,SAAS,KAAK;AACnC,aAAO;AAAA,QACL,GAAG,UAAU,YAAY,GAAG,wBAAwB,OAAO,MAAM,4BAA4B,GAAG,GAAG;AAAA,QACnG;AAAA,QACA,6BAA6B,IAAI;AAAA,QACjC,6BAA6B,IAAI;AAAA,QACjC,6CAA6C,GAAG;AAAA,QAChD,gCAAgC,GAAG;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW;AAAA,MACT,EAAE,MAAM,UAAU,aAAa,+CAA+C,UAAU,KAAK;AAAA,IAC/F;AAAA,IACA,OAAO,CAAC,SAAS;AACf,YAAM,SAAS,OAAO,KAAK,MAAM,QAAQ,CAAC;AAC1C,aAAO;AAAA,QACL,6BAA6B,MAAM;AAAA,QACnC;AAAA,QACA,4BAA4B,MAAM,kBAAkB,MAAM;AAAA,QAC1D,oCAAoC,MAAM,kBAAkB,MAAM;AAAA,QAClE,6CAA6C,MAAM,sBAAsB,MAAM;AAAA,QAC/E,uCAAuC,MAAM;AAAA,QAC7C;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW,CAAC;AAAA,IACZ,OAAO,MACL;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,EAAE,KAAK,IAAI;AAAA,EACf;AACF;AAEO,SAAS,WAAW,MAA4C;AACrE,SAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC5C;","names":[]}
1
+ {"version":3,"sources":["../src/plan-copy.ts","../src/prompts.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 * MCP prompts: ready-made workflows built on the Visual Sentinel tools.\n * Each prompt returns one user message that tells the assistant which tools to\n * call and how to report back. They work without an API key unless noted.\n */\n\nimport { API_KEY_PLANS, API_KEY_PLANS_SHORT } from './plan-copy.js';\n\nexport interface PromptArgument {\n name: string;\n description: string;\n required: boolean;\n}\n\nexport interface PromptDefinition {\n name: string;\n title: string;\n description: string;\n arguments: PromptArgument[];\n /** Builds the user message for the given arguments. */\n build: (args: Record<string, string | undefined>) => string;\n}\n\nfunction need(args: Record<string, string | undefined>, key: string): string {\n const v = args[key]?.trim();\n if (!v) throw new Error(`Argument \"${key}\" is required.`);\n return v;\n}\n\nfunction hostOf(value: string): string {\n return value.replace(/^https?:\\/\\//i, '').replace(/\\/.*$/, '');\n}\n\nexport const PROMPTS: PromptDefinition[] = [\n {\n name: 'check_website_health',\n title: 'Check a website\\'s health',\n description:\n 'Run uptime, SSL, DNS and speed checks on one site and summarise what needs attention. No account or API key needed.',\n arguments: [\n { name: 'url', description: 'Site to check, e.g. https://example.com', required: true },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n return [\n `Check the health of ${url} with the Visual Sentinel tools and report back in plain language.`,\n '',\n `1. vs_website_check with url \"${url}\": HTTP status, response time, server header.`,\n `2. vs_ssl_check with domain \"${host}\": certificate validity, expiry date and chain.`,\n `3. vs_dns_check with domain \"${host}\": A, AAAA, MX, NS and TXT records.`,\n `4. vs_speed_test with url \"${url}\": time to first byte, total load time, transfer size, redirects.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'investigate_broken_site',\n title: 'Investigate why my site looks broken',\n description:\n '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.',\n arguments: [\n { name: 'url', description: 'The affected page, e.g. https://example.com/pricing', required: true },\n { name: 'symptom', description: 'What you see, e.g. \"blank page\", \"images missing\", \"very slow\"', required: false },\n ],\n build: (args) => {\n const url = need(args, 'url');\n const host = hostOf(url);\n const symptom = args.symptom?.trim();\n return [\n `${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:`,\n '',\n `1. DNS: vs_dns_check for \"${host}\". Do the records resolve, and do they point where they should?`,\n `2. TLS: vs_ssl_check for \"${host}\". Is the certificate valid, unexpired and issued for this hostname?`,\n `3. Server response: vs_website_check for \"${url}\". Status code, response time, content snippet.`,\n `4. Speed: vs_speed_test for \"${url}\". Is time to first byte or load time unusually high, and is there a redirect chain?`,\n '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.',\n '',\n '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.',\n 'Report: the evidence from each step, the most likely cause, and the next action.',\n ].join('\\n');\n },\n },\n {\n name: 'pre_launch_checklist',\n title: 'Pre-launch checklist for a site',\n description:\n '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.',\n arguments: [\n { name: 'domain', description: 'The domain being launched, e.g. example.com', required: true },\n ],\n build: (args) => {\n const domain = hostOf(need(args, 'domain'));\n return [\n `Run a pre-launch check on ${domain} with the Visual Sentinel tools and return a checklist with Pass, Warn or Fail for each line.`,\n '',\n `- 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.`,\n `- Certificate: vs_ssl_check for \"${domain}\" and for \"www.${domain}\". Valid, issued for the hostname, and more than 30 days from expiry.`,\n `- Response: vs_website_check for \"https://${domain}\" and \"https://www.${domain}\". Both return 200 or one cleanly redirects to the other.`,\n `- Speed: vs_speed_test for \"https://${domain}\". Report time to first byte, total load time and redirect count.`,\n '',\n '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.',\n ].join('\\n');\n },\n },\n {\n name: 'triage_open_incidents',\n title: 'Triage open incidents',\n description:\n `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}).`,\n arguments: [],\n build: () =>\n [\n 'Review my Visual Sentinel account and tell me what needs attention first.',\n '',\n '1. vs_incidents_list with status OPEN, then vs_incidents_get for each one (up to 5, most recent first).',\n '2. vs_alerts_list with status UNACKNOWLEDGED.',\n '3. For each affected monitor, vs_monitors_get and vs_monitors_uptime for the last 24 hours.',\n '',\n `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.`,\n ].join('\\n'),\n },\n];\n\nexport function findPrompt(name: string): PromptDefinition | undefined {\n return PROMPTS.find((p) => p.name === name);\n}\n"],"mappings":";;;AAaO,IAAM,gBACX;AAGK,IAAM,sBAAsB;;;ACMnC,SAAS,KAAK,MAA0C,KAAqB;AAC3E,QAAM,IAAI,KAAK,GAAG,GAAG,KAAK;AAC1B,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,aAAa,GAAG,gBAAgB;AACxD,SAAO;AACT;AAEA,SAAS,OAAO,OAAuB;AACrC,SAAO,MAAM,QAAQ,iBAAiB,EAAE,EAAE,QAAQ,SAAS,EAAE;AAC/D;AAEO,IAAM,UAA8B;AAAA,EACzC;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW;AAAA,MACT,EAAE,MAAM,OAAO,aAAa,2CAA2C,UAAU,KAAK;AAAA,IACxF;AAAA,IACA,OAAO,CAAC,SAAS;AACf,YAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,YAAM,OAAO,OAAO,GAAG;AACvB,aAAO;AAAA,QACL,uBAAuB,GAAG;AAAA,QAC1B;AAAA,QACA,iCAAiC,GAAG;AAAA,QACpC,gCAAgC,IAAI;AAAA,QACpC,gCAAgC,IAAI;AAAA,QACpC,8BAA8B,GAAG;AAAA,QACjC;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW;AAAA,MACT,EAAE,MAAM,OAAO,aAAa,uDAAuD,UAAU,KAAK;AAAA,MAClG,EAAE,MAAM,WAAW,aAAa,kEAAkE,UAAU,MAAM;AAAA,IACpH;AAAA,IACA,OAAO,CAAC,SAAS;AACf,YAAM,MAAM,KAAK,MAAM,KAAK;AAC5B,YAAM,OAAO,OAAO,GAAG;AACvB,YAAM,UAAU,KAAK,SAAS,KAAK;AACnC,aAAO;AAAA,QACL,GAAG,UAAU,YAAY,GAAG,wBAAwB,OAAO,MAAM,4BAA4B,GAAG,GAAG;AAAA,QACnG;AAAA,QACA,6BAA6B,IAAI;AAAA,QACjC,6BAA6B,IAAI;AAAA,QACjC,6CAA6C,GAAG;AAAA,QAChD,gCAAgC,GAAG;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE;AAAA,IACF,WAAW;AAAA,MACT,EAAE,MAAM,UAAU,aAAa,+CAA+C,UAAU,KAAK;AAAA,IAC/F;AAAA,IACA,OAAO,CAAC,SAAS;AACf,YAAM,SAAS,OAAO,KAAK,MAAM,QAAQ,CAAC;AAC1C,aAAO;AAAA,QACL,6BAA6B,MAAM;AAAA,QACnC;AAAA,QACA,4BAA4B,MAAM,kBAAkB,MAAM;AAAA,QAC1D,oCAAoC,MAAM,kBAAkB,MAAM;AAAA,QAClE,6CAA6C,MAAM,sBAAsB,MAAM;AAAA,QAC/E,uCAAuC,MAAM;AAAA,QAC7C;AAAA,QACA;AAAA,MACF,EAAE,KAAK,IAAI;AAAA,IACb;AAAA,EACF;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,OAAO;AAAA,IACP,aACE,oIAAoI,mBAAmB;AAAA,IACzJ,WAAW,CAAC;AAAA,IACZ,OAAO,MACL;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,uSAAuS,aAAa;AAAA,IACtT,EAAE,KAAK,IAAI;AAAA,EACf;AACF;AAEO,SAAS,WAAW,MAA4C;AACrE,SAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AAC5C;","names":[]}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * MCP resources: read-only reference material the assistant can attach as
3
3
  * context. Static on purpose, so they work without an API key and never touch
4
- * account data. Plan limits come from the public pricing page; check
5
- * https://visualsentinel.com/pricing for current prices.
4
+ * account data. Plan prices and limits mirror the public pricing page as of
5
+ * PLANS_AS_OF; https://visualsentinel.com/pricing is always current.
6
6
  */
7
7
  interface ResourceDefinition {
8
8
  uri: string;
package/dist/resources.js CHANGED
@@ -537,22 +537,32 @@ var TOOLS = [
537
537
  }
538
538
  ];
539
539
 
540
+ // src/plan-copy.ts
541
+ var API_KEY_PLANS = "every paid plan (Solo, Starter, Business and Agency), including during the 10-day free trial";
542
+ var API_KEY_PLANS_SHORT = "every paid plan, including the 10-day free trial";
543
+ var NO_FREE_PLAN_SENTENCE = "Sign-ups on the website have no Free plan.";
544
+ var MONEY_BACK_SENTENCE = "Every paid plan has a 30-day money-back guarantee from the first payment.";
545
+
540
546
  // src/resources.ts
541
- var PLANS_AS_OF = "2026-10-16";
547
+ var PLANS_AS_OF = "2026-11-09";
542
548
  var PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})
543
549
 
544
- Prices and the current list are always at https://visualsentinel.com/pricing. There is no free plan for new accounts: a new workspace gets a first check of its site, then a 7-day trial (card required) or a paid plan.
550
+ 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.
551
+
552
+ Prices are in US dollars before tax. Yearly costs 10 months of monthly.
545
553
 
546
- | Plan | Monitors | Fastest uptime check | Visual pages | Status pages | API keys and MCP account tools |
547
- |---|---|---|---|---|---|
548
- | Solo | 10 | every 5 minutes | 1 site, every 30 minutes | 1 | no |
549
- | Starter | 15 | every 5 minutes | 5, every 15 minutes | 1 | no |
550
- | Business | 60 | every 1 minute | 60, every 15 minutes | 5 | yes |
551
- | Agency | 300 | every 1 minute | 300, every 15 minutes | 50 | yes |
554
+ | Plan | Price | Monitors | Fastest uptime check | Sites with screenshot checks | Status pages | API keys and MCP account tools |
555
+ |---|---|---|---|---|---|---|
556
+ | Solo | $15 a month or $150 a year | 10 | every 5 minutes | 1 site, up to 4 pages, every 30 minutes | 1 | yes |
557
+ | Starter | $30 a month or $300 a year | 15 | every 5 minutes | 5 sites, up to 6 pages each, every 15 minutes | 1 | yes |
558
+ | Business | $80 a month or $800 a year | 60 | every minute | 60 sites, up to 6 pages each, every 15 minutes | 5 | yes |
559
+ | Agency | $300 a month or $3,000 a year | 300 | every minute | 300 sites, up to 6 pages each, every 15 minutes | 50 | yes |
552
560
 
561
+ - 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.
553
562
  - The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.
554
- - The twelve account tools need an API key, which is available on the Business and Agency plans. Create one at https://visualsentinel.com/settings/api-keys.
555
- - Checks run from Europe and the US. Alert channels on every plan: email, Slack, Discord, Microsoft Teams, Telegram, PagerDuty, Opsgenie, webhooks, WhatsApp, Prometheus, and push notifications in the mobile app.
563
+ - The twelve account tools need an API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys.
564
+ - 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.
565
+ - 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.
556
566
  `;
557
567
  var HELP = `# Using the Visual Sentinel MCP server
558
568
 
@@ -562,7 +572,7 @@ Visual Sentinel catches pages that look broken even when the site is up. It watc
562
572
  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.
563
573
 
564
574
  ## Connect your account
565
- 1. Get an API key (Business and Agency plans): https://visualsentinel.com/settings/api-keys
575
+ 1. Get an API key (${API_KEY_PLANS_SHORT}): https://visualsentinel.com/settings/api-keys
566
576
  2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.
567
577
  3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.
568
578
 
@@ -578,7 +588,7 @@ check_website_health, investigate_broken_site, pre_launch_checklist and triage_o
578
588
  `;
579
589
  function toolsReference() {
580
590
  const rows = TOOLS.map(
581
- (t) => `| ${t.name} | ${t.requiresAuth ? "API key (Business and Agency)" : "none"} | ${t.annotations.readOnlyHint ? "read-only" : "changes data"} | ${(t.description.split(". ")[0] ?? "").replace(/\.$/, "")} |`
591
+ (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(/\.$/, "")} |`
582
592
  );
583
593
  return [
584
594
  "# Visual Sentinel tools",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tools.ts","../src/resources.ts"],"sourcesContent":["/**\n * Tool registry for the Visual Sentinel MCP server.\n *\n * Each tool wraps a single Visual Sentinel REST endpoint with a JSON Schema\n * input and a structured handler. Public-tool handlers (DNS check, SSL check,\n * speed test, website check, health) run without authentication; everything\n * else requires `VS_API_KEY`.\n */\n\nimport { ApiClient } from './api-client.js';\n\ninterface ToolInput {\n type: 'object';\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n}\n\nexport interface ToolAnnotations {\n title: string;\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface ToolDefinition {\n name: string;\n /** Human-readable title shown by MCP clients. */\n title: string;\n description: string;\n annotations: ToolAnnotations;\n inputSchema: ToolInput;\n requiresAuth: boolean;\n handler: (args: Record<string, unknown>, client: ApiClient) => Promise<unknown>;\n}\n\nconst STR = { type: 'string' as const };\nconst NUM = { type: 'number' as const };\nconst INT = { type: 'integer' as const };\nconst BOOL = { type: 'boolean' as const };\n\nfunction pickString(args: Record<string, unknown>, key: string): string | undefined {\n const v = args[key];\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction pickNumber(args: Record<string, unknown>, key: string): number | undefined {\n const v = args[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\n\nfunction requireString(args: Record<string, unknown>, key: string): string {\n const v = pickString(args, key);\n if (!v) throw new Error(`Argument \"${key}\" (string) is required.`);\n return v;\n}\n\nexport const TOOLS: ToolDefinition[] = [\n // -------------------------------------------------------------------------\n // Public tools (no auth)\n // -------------------------------------------------------------------------\n {\n name: 'vs_health',\n title: 'Check Visual Sentinel service health',\n annotations: {\n title: 'Check Visual Sentinel service health',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (_args, client) => client.request('GET', '/api/health', { auth: false }),\n },\n\n {\n name: 'vs_dns_check',\n title: 'Look up DNS records',\n annotations: {\n title: 'Look up DNS records',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel\\'s public DNS lookup tool. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Domain to resolve, e.g. example.com (without protocol).' },\n recordType: {\n ...STR,\n description: 'Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all.',\n },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/dns-check', {\n auth: false,\n query: {\n domain: requireString(args, 'domain'),\n // The API reads ?type= (not recordType).\n type: pickString(args, 'recordType'),\n },\n }),\n },\n\n {\n name: 'vs_ssl_check',\n title: 'Inspect an SSL certificate',\n annotations: {\n title: 'Inspect an SSL certificate',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Hostname to inspect, e.g. example.com (without protocol).' },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/ssl-check', {\n auth: false,\n // The API reads ?domain=. `host` stays as an input alias for 0.1.x callers.\n query: { domain: pickString(args, 'domain') ?? requireString(args, 'host') },\n }),\n },\n\n {\n name: 'vs_speed_test',\n title: 'Run a one-shot speed test',\n annotations: {\n title: 'Run a one-shot speed test',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/speed-test', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n {\n name: 'vs_website_check',\n title: 'Check a website',\n annotations: {\n title: 'Check a website',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/website-check', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n // -------------------------------------------------------------------------\n // Authenticated tools (require VS_API_KEY)\n // -------------------------------------------------------------------------\n {\n name: 'vs_monitors_list',\n title: 'List monitors',\n annotations: {\n title: 'List monitors',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'Filter: UP, DOWN, PAUSED, MAINTENANCE.' },\n type: {\n ...STR,\n description:\n 'Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n page: { ...INT, description: 'Page number (default 1).' },\n limit: { ...INT, description: 'Items per page (default 50, max 200).' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/monitors', {\n query: {\n status: pickString(args, 'status'),\n type: pickString(args, 'type'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_get',\n title: 'Get a monitor',\n annotations: {\n title: 'Get a monitor',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch a single monitor by id, including its current status, last check, and configuration.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id (cuid).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_monitors_create',\n title: 'Create a monitor',\n annotations: {\n title: 'Create a monitor',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { ...STR, description: 'Display name for the monitor.' },\n url: { ...STR, description: 'Full URL to monitor (or hostname for PING/PORT/DNS/SSL).' },\n type: {\n ...STR,\n description:\n 'Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n interval: { ...INT, description: 'Check interval in seconds (60, 300, 600, 1800, 3600).' },\n timeout: { ...INT, description: 'Per-check timeout in seconds (default 30).' },\n method: { ...STR, description: 'HTTP method for HTTP/HTTPS monitors (default GET).' },\n keyword: { ...STR, description: 'Keyword to assert for KEYWORD monitors.' },\n port: { ...INT, description: 'TCP port for PORT monitors.' },\n },\n required: ['name', 'url', 'type'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', '/api/monitors', {\n body: {\n name: requireString(args, 'name'),\n url: requireString(args, 'url'),\n type: requireString(args, 'type'),\n interval: pickNumber(args, 'interval'),\n timeout: pickNumber(args, 'timeout'),\n method: pickString(args, 'method'),\n keyword: pickString(args, 'keyword'),\n port: pickNumber(args, 'port'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_check_now',\n title: 'Run a monitor check now',\n annotations: {\n title: 'Run a monitor check now',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/check`),\n },\n\n {\n name: 'vs_monitors_uptime',\n title: 'Get monitor uptime',\n annotations: {\n title: 'Get monitor uptime',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch the uptime percentage and time-series breakdown for a monitor. Use `window` for a preset window (24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n // Precedence mirrors the API: all > from/to > window/period.\n const query = args['all'] === true\n ? { all: true }\n : (from && to)\n ? { from, to }\n : { period: pickString(args, 'window') ?? '30d' };\n return client.request(\n 'GET',\n `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/uptime`,\n { query },\n );\n },\n },\n\n {\n name: 'vs_monitors_metrics_bulk',\n title: 'Get bulk monitor metrics',\n annotations: {\n title: 'Get bulk monitor metrics',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch uptime or response-time metrics for up to 100 monitors in a single request. Use `window` for a preset window (1h, 6h, 24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history. Returns a `monitors` object keyed by monitor id.',\n inputSchema: {\n type: 'object',\n properties: {\n monitorIds: {\n type: 'array',\n items: { type: 'string' },\n description: 'List of monitor IDs to fetch metrics for (max 100).',\n },\n metric: {\n ...STR,\n description: 'Metric to return: \"uptime\" or \"response-time\".',\n },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['monitorIds', 'metric'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const monitorIds = args['monitorIds'];\n if (!Array.isArray(monitorIds) || monitorIds.length === 0) {\n throw new Error('Argument \"monitorIds\" must be a non-empty array.');\n }\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n const body: Record<string, unknown> = {\n monitorIds,\n metric: requireString(args, 'metric'),\n };\n // Precedence mirrors the API: all > from/to > window/period.\n if (args['all'] === true) {\n body['all'] = true;\n } else if (from && to) {\n body['from'] = from;\n body['to'] = to;\n } else {\n body['period'] = pickString(args, 'window') ?? '30d';\n }\n return client.request('POST', '/api/monitors/metrics', { body });\n },\n },\n\n {\n name: 'vs_incidents_list',\n title: 'List incidents',\n annotations: {\n title: 'List incidents',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'OPEN or RESOLVED.' },\n monitorId: { ...STR, description: 'Filter to one monitor.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/incidents', {\n query: {\n status: pickString(args, 'status'),\n monitorId: pickString(args, 'monitorId'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_incidents_get',\n title: 'Get an incident',\n annotations: {\n title: 'Get an incident',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Fetch a single incident with its full check history and root-cause hints.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Incident id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/incidents/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_alerts_list',\n title: 'List alerts',\n annotations: {\n title: 'List alerts',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/alerts', {\n query: {\n status: pickString(args, 'status'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_alerts_acknowledge',\n title: 'Acknowledge an alert',\n annotations: {\n title: 'Acknowledge an alert',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Acknowledge an alert by id. The acknowledgement is recorded against the account that authorised this connection.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Alert id.' },\n note: { ...STR, description: 'Optional acknowledgement note (visible in alert history).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/alerts/${encodeURIComponent(requireString(args, 'id'))}/acknowledge`, {\n body: { note: pickString(args, 'note') },\n }),\n },\n\n {\n name: 'vs_status_pages_list',\n title: 'List status pages',\n annotations: {\n title: 'List status pages',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List public-facing status pages owned by the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (_args, client) => client.request('GET', '/api/status-pages'),\n },\n\n {\n name: 'vs_servers_list',\n title: 'List servers',\n annotations: {\n title: 'List servers',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List servers (origins) registered in the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {\n page: { ...INT },\n limit: { ...INT, description: 'Default 50.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/servers', {\n query: { page: pickNumber(args, 'page'), limit: pickNumber(args, 'limit') },\n }),\n },\n];\n\nexport function findTool(name: string): ToolDefinition | undefined {\n return TOOLS.find((t) => t.name === name);\n}\n\n// Suppress unused-warning suppression: BOOL/NUM are reserved for future tools.\nvoid BOOL;\nvoid NUM;\n","/**\n * MCP resources: read-only reference material the assistant can attach as\n * context. Static on purpose, so they work without an API key and never touch\n * account data. Plan limits come from the public pricing page; check\n * https://visualsentinel.com/pricing for current prices.\n */\n\nimport { TOOLS } from './tools.js';\n\nexport interface ResourceDefinition {\n uri: string;\n name: string;\n title: string;\n description: string;\n mimeType: string;\n read: () => string;\n}\n\nconst PLANS_AS_OF = '2026-10-16';\n\nconst PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})\n\nPrices and the current list are always at https://visualsentinel.com/pricing. There is no free plan for new accounts: a new workspace gets a first check of its site, then a 7-day trial (card required) or a paid plan.\n\n| Plan | Monitors | Fastest uptime check | Visual pages | Status pages | API keys and MCP account tools |\n|---|---|---|---|---|---|\n| Solo | 10 | every 5 minutes | 1 site, every 30 minutes | 1 | no |\n| Starter | 15 | every 5 minutes | 5, every 15 minutes | 1 | no |\n| Business | 60 | every 1 minute | 60, every 15 minutes | 5 | yes |\n| Agency | 300 | every 1 minute | 300, every 15 minutes | 50 | yes |\n\n- The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.\n- The twelve account tools need an API key, which is available on the Business and Agency plans. Create one at https://visualsentinel.com/settings/api-keys.\n- Checks run from Europe and the US. Alert channels on every plan: email, Slack, Discord, Microsoft Teams, Telegram, PagerDuty, Opsgenie, webhooks, WhatsApp, Prometheus, and push notifications in the mobile app.\n`;\n\nconst HELP = `# Using the Visual Sentinel MCP server\n\nVisual Sentinel catches pages that look broken even when the site is up. It watches uptime, performance, SSL certificates, DNS, visual changes and content changes.\n\n## Start without an account\nAsk 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.\n\n## Connect your account\n1. Get an API key (Business and Agency plans): https://visualsentinel.com/settings/api-keys\n2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.\n3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.\n\n## Ready-made prompts\ncheck_website_health, investigate_broken_site, pre_launch_checklist and triage_open_incidents.\n\n## Links\n- Docs and install: https://visualsentinel.com/mcp\n- API reference: https://visualsentinel.com/docs\n- Pricing: https://visualsentinel.com/pricing\n- Source: https://github.com/VisualSentinel/mcp-server\n- Support: https://visualsentinel.com/contact\n`;\n\nfunction toolsReference(): string {\n const rows = TOOLS.map(\n (t) => `| ${t.name} | ${t.requiresAuth ? 'API key (Business and Agency)' : 'none'} | ${t.annotations.readOnlyHint ? 'read-only' : 'changes data'} | ${(t.description.split('. ')[0] ?? '').replace(/\\.$/, '')} |`,\n );\n return [\n '# Visual Sentinel tools',\n '',\n '| Tool | Needs | Type | What it does |',\n '|---|---|---|---|',\n ...rows,\n '',\n ].join('\\n');\n}\n\nexport const RESOURCES: ResourceDefinition[] = [\n {\n uri: 'visualsentinel://plans',\n name: 'plans',\n title: 'Plans and limits',\n description: 'Monitor counts, check frequencies and which plans include API keys and the MCP account tools.',\n mimeType: 'text/markdown',\n read: () => PLANS,\n },\n {\n uri: 'visualsentinel://help',\n name: 'help',\n title: 'Help and setup',\n description: 'How to use the server without an account, how to add an API key, and where the docs are.',\n mimeType: 'text/markdown',\n read: () => HELP,\n },\n {\n uri: 'visualsentinel://tools',\n name: 'tools',\n title: 'Tool reference',\n description: 'Every tool, whether it needs an API key, and whether it is read-only.',\n mimeType: 'text/markdown',\n read: toolsReference,\n },\n];\n\nexport function findResource(uri: string): ResourceDefinition | undefined {\n return RESOURCES.find((r) => r.uri === uri);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/tools.ts","../src/plan-copy.ts","../src/resources.ts"],"sourcesContent":["/**\n * Tool registry for the Visual Sentinel MCP server.\n *\n * Each tool wraps a single Visual Sentinel REST endpoint with a JSON Schema\n * input and a structured handler. Public-tool handlers (DNS check, SSL check,\n * speed test, website check, health) run without authentication; everything\n * else requires `VS_API_KEY`.\n */\n\nimport { ApiClient } from './api-client.js';\n\ninterface ToolInput {\n type: 'object';\n properties: Record<string, unknown>;\n required?: string[];\n additionalProperties?: boolean;\n}\n\nexport interface ToolAnnotations {\n title: string;\n readOnlyHint: boolean;\n destructiveHint: boolean;\n idempotentHint: boolean;\n openWorldHint: boolean;\n}\n\nexport interface ToolDefinition {\n name: string;\n /** Human-readable title shown by MCP clients. */\n title: string;\n description: string;\n annotations: ToolAnnotations;\n inputSchema: ToolInput;\n requiresAuth: boolean;\n handler: (args: Record<string, unknown>, client: ApiClient) => Promise<unknown>;\n}\n\nconst STR = { type: 'string' as const };\nconst NUM = { type: 'number' as const };\nconst INT = { type: 'integer' as const };\nconst BOOL = { type: 'boolean' as const };\n\nfunction pickString(args: Record<string, unknown>, key: string): string | undefined {\n const v = args[key];\n return typeof v === 'string' && v.length > 0 ? v : undefined;\n}\n\nfunction pickNumber(args: Record<string, unknown>, key: string): number | undefined {\n const v = args[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n}\n\nfunction requireString(args: Record<string, unknown>, key: string): string {\n const v = pickString(args, key);\n if (!v) throw new Error(`Argument \"${key}\" (string) is required.`);\n return v;\n}\n\nexport const TOOLS: ToolDefinition[] = [\n // -------------------------------------------------------------------------\n // Public tools (no auth)\n // -------------------------------------------------------------------------\n {\n name: 'vs_health',\n title: 'Check Visual Sentinel service health',\n annotations: {\n title: 'Check Visual Sentinel service health',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (_args, client) => client.request('GET', '/api/health', { auth: false }),\n },\n\n {\n name: 'vs_dns_check',\n title: 'Look up DNS records',\n annotations: {\n title: 'Look up DNS records',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel\\'s public DNS lookup tool. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Domain to resolve, e.g. example.com (without protocol).' },\n recordType: {\n ...STR,\n description: 'Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all.',\n },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/dns-check', {\n auth: false,\n query: {\n domain: requireString(args, 'domain'),\n // The API reads ?type= (not recordType).\n type: pickString(args, 'recordType'),\n },\n }),\n },\n\n {\n name: 'vs_ssl_check',\n title: 'Inspect an SSL certificate',\n annotations: {\n title: 'Inspect an SSL certificate',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n domain: { ...STR, description: 'Hostname to inspect, e.g. example.com (without protocol).' },\n },\n required: ['domain'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/ssl-check', {\n auth: false,\n // The API reads ?domain=. `host` stays as an input alias for 0.1.x callers.\n query: { domain: pickString(args, 'domain') ?? requireString(args, 'host') },\n }),\n },\n\n {\n name: 'vs_speed_test',\n title: 'Run a one-shot speed test',\n annotations: {\n title: 'Run a one-shot speed test',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/speed-test', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n {\n name: 'vs_website_check',\n title: 'Check a website',\n annotations: {\n title: 'Check a website',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: true,\n },\n description:\n 'Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { ...STR, description: 'Full URL to test, including protocol (https://...).' },\n },\n required: ['url'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/website-check', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n // -------------------------------------------------------------------------\n // Authenticated tools (require VS_API_KEY)\n // -------------------------------------------------------------------------\n {\n name: 'vs_monitors_list',\n title: 'List monitors',\n annotations: {\n title: 'List monitors',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'Filter: UP, DOWN, PAUSED, MAINTENANCE.' },\n type: {\n ...STR,\n description:\n 'Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n page: { ...INT, description: 'Page number (default 1).' },\n limit: { ...INT, description: 'Items per page (default 50, max 200).' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/monitors', {\n query: {\n status: pickString(args, 'status'),\n type: pickString(args, 'type'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_get',\n title: 'Get a monitor',\n annotations: {\n title: 'Get a monitor',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch a single monitor by id, including its current status, last check, and configuration.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id (cuid).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_monitors_create',\n title: 'Create a monitor',\n annotations: {\n title: 'Create a monitor',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { ...STR, description: 'Display name for the monitor.' },\n url: { ...STR, description: 'Full URL to monitor (or hostname for PING/PORT/DNS/SSL).' },\n type: {\n ...STR,\n description:\n 'Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET.',\n },\n interval: { ...INT, description: 'Check interval in seconds (60, 300, 600, 1800, 3600).' },\n timeout: { ...INT, description: 'Per-check timeout in seconds (default 30).' },\n method: { ...STR, description: 'HTTP method for HTTP/HTTPS monitors (default GET).' },\n keyword: { ...STR, description: 'Keyword to assert for KEYWORD monitors.' },\n port: { ...INT, description: 'TCP port for PORT monitors.' },\n },\n required: ['name', 'url', 'type'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', '/api/monitors', {\n body: {\n name: requireString(args, 'name'),\n url: requireString(args, 'url'),\n type: requireString(args, 'type'),\n interval: pickNumber(args, 'interval'),\n timeout: pickNumber(args, 'timeout'),\n method: pickString(args, 'method'),\n keyword: pickString(args, 'keyword'),\n port: pickNumber(args, 'port'),\n },\n }),\n },\n\n {\n name: 'vs_monitors_check_now',\n title: 'Run a monitor check now',\n annotations: {\n title: 'Run a monitor check now',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n },\n description:\n 'Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/check`),\n },\n\n {\n name: 'vs_monitors_uptime',\n title: 'Get monitor uptime',\n annotations: {\n title: 'Get monitor uptime',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch the uptime percentage and time-series breakdown for a monitor. Use `window` for a preset window (24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n // Precedence mirrors the API: all > from/to > window/period.\n const query = args['all'] === true\n ? { all: true }\n : (from && to)\n ? { from, to }\n : { period: pickString(args, 'window') ?? '30d' };\n return client.request(\n 'GET',\n `/api/monitors/${encodeURIComponent(requireString(args, 'id'))}/uptime`,\n { query },\n );\n },\n },\n\n {\n name: 'vs_monitors_metrics_bulk',\n title: 'Get bulk monitor metrics',\n annotations: {\n title: 'Get bulk monitor metrics',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'Fetch uptime or response-time metrics for up to 100 monitors in a single request. Use `window` for a preset window (1h, 6h, 24h, 7d, 30d, 90d, 365d; default 30d), supply both `from` and `to` (ISO 8601) for a custom date range, or set `all: true` for the full available history. Returns a `monitors` object keyed by monitor id.',\n inputSchema: {\n type: 'object',\n properties: {\n monitorIds: {\n type: 'array',\n items: { type: 'string' },\n description: 'List of monitor IDs to fetch metrics for (max 100).',\n },\n metric: {\n ...STR,\n description: 'Metric to return: \"uptime\" or \"response-time\".',\n },\n window: {\n ...STR,\n description: 'Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied.',\n },\n from: {\n ...STR,\n description: 'ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`.',\n },\n to: {\n ...STR,\n description: 'ISO 8601 end of a custom range. Must be supplied with `from`.',\n },\n all: {\n type: 'boolean',\n description: 'Return the full available history from the earliest stored data. Takes precedence over from/to and window.',\n },\n },\n required: ['monitorIds', 'metric'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) => {\n const monitorIds = args['monitorIds'];\n if (!Array.isArray(monitorIds) || monitorIds.length === 0) {\n throw new Error('Argument \"monitorIds\" must be a non-empty array.');\n }\n const from = pickString(args, 'from');\n const to = pickString(args, 'to');\n const body: Record<string, unknown> = {\n monitorIds,\n metric: requireString(args, 'metric'),\n };\n // Precedence mirrors the API: all > from/to > window/period.\n if (args['all'] === true) {\n body['all'] = true;\n } else if (from && to) {\n body['from'] = from;\n body['to'] = to;\n } else {\n body['period'] = pickString(args, 'window') ?? '30d';\n }\n return client.request('POST', '/api/monitors/metrics', { body });\n },\n },\n\n {\n name: 'vs_incidents_list',\n title: 'List incidents',\n annotations: {\n title: 'List incidents',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'OPEN or RESOLVED.' },\n monitorId: { ...STR, description: 'Filter to one monitor.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/incidents', {\n query: {\n status: pickString(args, 'status'),\n monitorId: pickString(args, 'monitorId'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_incidents_get',\n title: 'Get an incident',\n annotations: {\n title: 'Get an incident',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Fetch a single incident with its full check history and root-cause hints.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Incident id.' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', `/api/incidents/${encodeURIComponent(requireString(args, 'id'))}`),\n },\n\n {\n name: 'vs_alerts_list',\n title: 'List alerts',\n annotations: {\n title: 'List alerts',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description:\n 'List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.',\n inputSchema: {\n type: 'object',\n properties: {\n status: { ...STR, description: 'UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED.' },\n page: { ...INT },\n limit: { ...INT, description: 'Default 50, max 200.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/alerts', {\n query: {\n status: pickString(args, 'status'),\n page: pickNumber(args, 'page'),\n limit: pickNumber(args, 'limit'),\n },\n }),\n },\n\n {\n name: 'vs_alerts_acknowledge',\n title: 'Acknowledge an alert',\n annotations: {\n title: 'Acknowledge an alert',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'Acknowledge an alert by id. The acknowledgement is recorded against the account that authorised this connection.',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Alert id.' },\n note: { ...STR, description: 'Optional acknowledgement note (visible in alert history).' },\n },\n required: ['id'],\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('POST', `/api/alerts/${encodeURIComponent(requireString(args, 'id'))}/acknowledge`, {\n body: { note: pickString(args, 'note') },\n }),\n },\n\n {\n name: 'vs_status_pages_list',\n title: 'List status pages',\n annotations: {\n title: 'List status pages',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List public-facing status pages owned by the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {},\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (_args, client) => client.request('GET', '/api/status-pages'),\n },\n\n {\n name: 'vs_servers_list',\n title: 'List servers',\n annotations: {\n title: 'List servers',\n readOnlyHint: true,\n destructiveHint: false,\n idempotentHint: true,\n openWorldHint: false,\n },\n description: 'List servers (origins) registered in the authenticated organization.',\n inputSchema: {\n type: 'object',\n properties: {\n page: { ...INT },\n limit: { ...INT, description: 'Default 50.' },\n },\n additionalProperties: false,\n },\n requiresAuth: true,\n handler: async (args, client) =>\n client.request('GET', '/api/servers', {\n query: { page: pickNumber(args, 'page'), limit: pickNumber(args, 'limit') },\n }),\n },\n];\n\nexport function findTool(name: string): ToolDefinition | undefined {\n return TOOLS.find((t) => t.name === name);\n}\n\n// Suppress unused-warning suppression: BOOL/NUM are reserved for future tools.\nvoid BOOL;\nvoid NUM;\n","/**\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 * MCP resources: read-only reference material the assistant can attach as\n * context. Static on purpose, so they work without an API key and never touch\n * account data. Plan prices and limits mirror the public pricing page as of\n * PLANS_AS_OF; https://visualsentinel.com/pricing is always current.\n */\n\nimport { TOOLS } from './tools.js';\nimport { API_KEY_PLANS, API_KEY_PLANS_SHORT, MONEY_BACK_SENTENCE, NO_FREE_PLAN_SENTENCE } from './plan-copy.js';\n\nexport interface ResourceDefinition {\n uri: string;\n name: string;\n title: string;\n description: string;\n mimeType: string;\n read: () => string;\n}\n\nconst PLANS_AS_OF = '2026-11-09';\n\nconst PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})\n\nPrices 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.\n\nPrices are in US dollars before tax. Yearly costs 10 months of monthly.\n\n| Plan | Price | Monitors | Fastest uptime check | Sites with screenshot checks | Status pages | API keys and MCP account tools |\n|---|---|---|---|---|---|---|\n| Solo | $15 a month or $150 a year | 10 | every 5 minutes | 1 site, up to 4 pages, every 30 minutes | 1 | yes |\n| Starter | $30 a month or $300 a year | 15 | every 5 minutes | 5 sites, up to 6 pages each, every 15 minutes | 1 | yes |\n| Business | $80 a month or $800 a year | 60 | every minute | 60 sites, up to 6 pages each, every 15 minutes | 5 | yes |\n| Agency | $300 a month or $3,000 a year | 300 | every minute | 300 sites, up to 6 pages each, every 15 minutes | 50 | yes |\n\n- 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.\n- The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.\n- The twelve account tools need an API key. API keys come with ${API_KEY_PLANS}. Create one at https://visualsentinel.com/settings/api-keys.\n- 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.\n- 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.\n`;\n\nconst HELP = `# Using the Visual Sentinel MCP server\n\nVisual Sentinel catches pages that look broken even when the site is up. It watches uptime, performance, SSL certificates, DNS, visual changes and content changes.\n\n## Start without an account\nAsk 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.\n\n## Connect your account\n1. Get an API key (${API_KEY_PLANS_SHORT}): https://visualsentinel.com/settings/api-keys\n2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.\n3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.\n\n## Ready-made prompts\ncheck_website_health, investigate_broken_site, pre_launch_checklist and triage_open_incidents.\n\n## Links\n- Docs and install: https://visualsentinel.com/mcp\n- API reference: https://visualsentinel.com/docs\n- Pricing: https://visualsentinel.com/pricing\n- Source: https://github.com/VisualSentinel/mcp-server\n- Support: https://visualsentinel.com/contact\n`;\n\nfunction toolsReference(): string {\n const rows = TOOLS.map(\n (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(/\\.$/, '')} |`,\n );\n return [\n '# Visual Sentinel tools',\n '',\n '| Tool | Needs | Type | What it does |',\n '|---|---|---|---|',\n ...rows,\n '',\n ].join('\\n');\n}\n\nexport const RESOURCES: ResourceDefinition[] = [\n {\n uri: 'visualsentinel://plans',\n name: 'plans',\n title: 'Plans and limits',\n description: 'Monitor counts, check frequencies and which plans include API keys and the MCP account tools.',\n mimeType: 'text/markdown',\n read: () => PLANS,\n },\n {\n uri: 'visualsentinel://help',\n name: 'help',\n title: 'Help and setup',\n description: 'How to use the server without an account, how to add an API key, and where the docs are.',\n mimeType: 'text/markdown',\n read: () => HELP,\n },\n {\n uri: 'visualsentinel://tools',\n name: 'tools',\n title: 'Tool reference',\n description: 'Every tool, whether it needs an API key, and whether it is read-only.',\n mimeType: 'text/markdown',\n read: toolsReference,\n },\n];\n\nexport function findResource(uri: string): ResourceDefinition | undefined {\n return RESOURCES.find((r) => r.uri === uri);\n}\n"],"mappings":"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","names":[]}
package/dist/version.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  /** Keep in sync with package.json (a test checks it). */
2
- declare const SERVER_VERSION = "0.2.2";
2
+ declare const SERVER_VERSION = "0.2.3";
3
3
 
4
4
  export { SERVER_VERSION };
package/dist/version.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/version.ts
4
- var SERVER_VERSION = "0.2.2";
4
+ var SERVER_VERSION = "0.2.3";
5
5
  export {
6
6
  SERVER_VERSION
7
7
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/** Keep in sync with package.json (a test checks it). */\nexport const SERVER_VERSION = '0.2.2';\n"],"mappings":";;;AACO,IAAM,iBAAiB;","names":[]}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["/** Keep in sync with package.json (a test checks it). */\nexport const SERVER_VERSION = '0.2.3';\n"],"mappings":";;;AACO,IAAM,iBAAiB;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@visualsentinel/mcp-server",
3
- "version": "0.2.2",
4
- "description": "Model Context Protocol server for Visual Sentinel website monitoring. Catches pages that look broken even when the site is up: uptime, performance, SSL, DNS, visual regression and content change, from Claude, Cursor, Cline, Continue and any MCP client. Five tools need no account; account tools need an API key (Business and Agency plans).",
3
+ "version": "0.2.3",
4
+ "description": "MCP server for Visual Sentinel website monitoring: catches pages that look broken even when the site is up. Uptime, speed, SSL, DNS, visual and content checks. Five tools need no account; account tools need an API key (every paid plan, trial included).",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "visualsentinel-mcp-server": "dist/index.js"
@@ -20,7 +20,7 @@
20
20
  "start": "node dist/index.js",
21
21
  "lint": "tsc --noEmit",
22
22
  "test": "node --test tests/*.test.mjs",
23
- "prepublishOnly": "npm run lint && npm run build"
23
+ "prepublishOnly": "npm run lint && npm run build && npm test"
24
24
  },
25
25
  "keywords": [
26
26
  "ai-agents",
File without changes