@visualsentinel/mcp-server 0.1.0

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 ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
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
+
5
+ ## [0.1.0] - 2026-04-29
6
+
7
+ Initial public release.
8
+
9
+ ### Added
10
+
11
+ * 16 tools wrapping the public Visual Sentinel REST API (5 public, 11 authenticated).
12
+ * stdio transport (MCP protocol version `2025-06-18`).
13
+ * `VS_API_KEY` (or `VISUALSENTINEL_API_KEY`) for authenticated tools; `VS_API_BASE` override for local development.
14
+ * TypeScript build via tsup, ESM-only, Node 18+.
15
+ * README with installation snippets for Claude Desktop, Claude Code, Cline, Continue.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Visual Sentinel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # Visual Sentinel MCP Server
2
+
3
+ Model Context Protocol server for [Visual Sentinel](https://visualsentinel.com), the six-layer website monitoring platform: uptime, performance, SSL, DNS, visual regression, and content change detection.
4
+
5
+ Connect Claude Desktop, Claude Code, Cline, Continue, or any MCP-compatible client to your Visual Sentinel account so an AI assistant can list monitors, create them, fetch incident history, run free DNS / SSL / speed checks, and acknowledge alerts on your behalf.
6
+
7
+ * **npm:** `@visualsentinel/mcp-server`
8
+ * **OpenAPI spec:** https://github.com/VisualSentinel/openapi
9
+ * **Get an API key:** https://visualsentinel.com/settings/api-keys
10
+ * **Hosted API reference:** https://visualsentinel.com/docs
11
+
12
+ ## What you get
13
+
14
+ 16 tools spanning the public Visual Sentinel API:
15
+
16
+ **Public tools (no API key required)**
17
+
18
+ | Tool | Endpoint | Purpose |
19
+ |---|---|---|
20
+ | `vs_health` | `GET /api/health` | Service health probe |
21
+ | `vs_dns_check` | `GET /api/tools/dns-check` | Resolve A / AAAA / MX / NS / TXT / CNAME / SOA records |
22
+ | `vs_ssl_check` | `GET /api/tools/ssl-check` | Inspect TLS certificate (issuer, validity, SAN list, chain) |
23
+ | `vs_speed_test` | `GET /api/tools/speed-test` | One-shot performance check (TTFB, total load, transfer size) |
24
+ | `vs_website_check` | `GET /api/tools/website-check` | Quick HTTP health check |
25
+
26
+ **Authenticated tools (require `VS_API_KEY`)**
27
+
28
+ | Tool | Endpoint | Purpose |
29
+ |---|---|---|
30
+ | `vs_monitors_list` | `GET /api/monitors` | List monitors with optional status / type filters |
31
+ | `vs_monitors_get` | `GET /api/monitors/{id}` | Fetch a single monitor with current status |
32
+ | `vs_monitors_create` | `POST /api/monitors` | Create a new monitor |
33
+ | `vs_monitors_check_now` | `POST /api/monitors/{id}/check` | Trigger an immediate check |
34
+ | `vs_monitors_uptime` | `GET /api/monitors/{id}/uptime` | Uptime percentage over 24h / 7d / 30d / 90d / 365d |
35
+ | `vs_incidents_list` | `GET /api/incidents` | List incidents, filterable by status or monitor |
36
+ | `vs_incidents_get` | `GET /api/incidents/{id}` | Fetch an incident with full check history |
37
+ | `vs_alerts_list` | `GET /api/alerts` | List alerts by status |
38
+ | `vs_alerts_acknowledge` | `POST /api/alerts/{id}/acknowledge` | Acknowledge an alert |
39
+ | `vs_status_pages_list` | `GET /api/status-pages` | List your public status pages |
40
+ | `vs_servers_list` | `GET /api/servers` | List registered origin servers |
41
+
42
+ ## Installation
43
+
44
+ ### Claude Desktop
45
+
46
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
47
+
48
+ ```json
49
+ {
50
+ "mcpServers": {
51
+ "visualsentinel": {
52
+ "command": "npx",
53
+ "args": ["-y", "@visualsentinel/mcp-server"],
54
+ "env": {
55
+ "VS_API_KEY": "vs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ Restart Claude Desktop. The 16 tools appear under the tools menu.
63
+
64
+ ### Claude Code (CLI)
65
+
66
+ ```bash
67
+ claude mcp add visualsentinel \
68
+ -- npx -y @visualsentinel/mcp-server
69
+ ```
70
+
71
+ Then export `VS_API_KEY` in the shell where Claude Code runs.
72
+
73
+ ### Cline (VS Code extension)
74
+
75
+ In Cline's MCP settings, add:
76
+
77
+ ```json
78
+ {
79
+ "visualsentinel": {
80
+ "command": "npx",
81
+ "args": ["-y", "@visualsentinel/mcp-server"],
82
+ "env": { "VS_API_KEY": "vs_live_..." }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Continue
88
+
89
+ In `~/.continue/config.json`:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": [
94
+ {
95
+ "name": "visualsentinel",
96
+ "command": "npx",
97
+ "args": ["-y", "@visualsentinel/mcp-server"],
98
+ "env": { "VS_API_KEY": "vs_live_..." }
99
+ }
100
+ ]
101
+ }
102
+ ```
103
+
104
+ ### Generic MCP client (stdio)
105
+
106
+ ```bash
107
+ VS_API_KEY=vs_live_xxx npx -y @visualsentinel/mcp-server
108
+ ```
109
+
110
+ The server speaks JSON-RPC over stdio per the MCP specification (`2025-06-18`).
111
+
112
+ ## Configuration
113
+
114
+ Two environment variables, both optional except in the noted cases:
115
+
116
+ | Variable | Default | Purpose |
117
+ |---|---|---|
118
+ | `VS_API_KEY` | (none) | Required for the 11 authenticated tools. Public tools work without it. Generate at https://visualsentinel.com/settings/api-keys. |
119
+ | `VS_API_BASE` | `https://visualsentinel.com` | Override only for testing against a local development instance. |
120
+
121
+ If a tool that needs auth is called without `VS_API_KEY`, the server returns a clear error rather than silently failing.
122
+
123
+ `VISUALSENTINEL_API_KEY` is accepted as an alias for `VS_API_KEY` for clients that prefer the longer name.
124
+
125
+ ## Usage examples
126
+
127
+ After installation, ask the AI assistant things like:
128
+
129
+ * "What's the SSL certificate situation on visualsentinel.com? When does it expire?"
130
+ * "List my Visual Sentinel monitors that are currently down."
131
+ * "Add a new HTTPS monitor for https://shop.example.com checking every minute."
132
+ * "Show me incidents from the last 24 hours and acknowledge the ones for monitor-id `clm123`."
133
+ * "What's the uptime percentage for my landing page over the last 30 days?"
134
+ * "Run a speed test on https://example.com/checkout."
135
+
136
+ The assistant chooses the appropriate tool, fills in arguments from the conversation, and shows the JSON response.
137
+
138
+ ## Security notes
139
+
140
+ * Your API key never leaves the machine running this MCP server. Requests go directly to `https://visualsentinel.com` over HTTPS.
141
+ * Public tools (`vs_health`, `vs_dns_check`, `vs_ssl_check`, `vs_speed_test`, `vs_website_check`) work without an API key, but their results are visible to anyone who can read your conversation logs. Treat them like any other shell-out.
142
+ * Revoke a leaked API key immediately at https://visualsentinel.com/settings/api-keys. Revocation takes effect within seconds.
143
+ * Tools that mutate state (`vs_monitors_create`, `vs_monitors_check_now`, `vs_alerts_acknowledge`) inherit your API key's role-based permissions. A read-only key cannot create monitors.
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ git clone https://github.com/VisualSentinel/mcp-server.git
149
+ cd mcp-server
150
+ npm install
151
+ npm run build # tsup, output to dist/
152
+ npm run lint # tsc --noEmit
153
+ npm run dev # tsup --watch
154
+ ```
155
+
156
+ To smoke-test locally against the production API:
157
+
158
+ ```bash
159
+ VS_API_KEY=vs_live_... node dist/index.js
160
+ ```
161
+
162
+ The server speaks JSON-RPC on stdin / stdout. Pipe in a `tools/list` request to see the registered tools.
163
+
164
+ ## Versioning
165
+
166
+ Semantic versioning. Breaking changes to tool names or required arguments get a major bump. Adding new tools or new optional arguments is a minor or patch.
167
+
168
+ The server's tool surface tracks the public Visual Sentinel API spec at https://github.com/VisualSentinel/openapi. When the upstream API gains a useful endpoint, this repository typically catches up within a release.
169
+
170
+ ## License
171
+
172
+ MIT. See [`LICENSE`](./LICENSE).
173
+
174
+ The Visual Sentinel API service itself is governed by https://visualsentinel.com/terms.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,463 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ import {
7
+ CallToolRequestSchema,
8
+ ListToolsRequestSchema
9
+ } from "@modelcontextprotocol/sdk/types.js";
10
+
11
+ // src/api-client.ts
12
+ var DEFAULT_BASE = "https://visualsentinel.com";
13
+ var VsApiError = class extends Error {
14
+ constructor(message, status, body) {
15
+ super(message);
16
+ this.status = status;
17
+ this.body = body;
18
+ this.name = "VsApiError";
19
+ }
20
+ status;
21
+ body;
22
+ };
23
+ var ApiClient = class {
24
+ apiKey;
25
+ baseUrl;
26
+ userAgent;
27
+ constructor(opts = {}) {
28
+ this.apiKey = opts.apiKey ?? process.env.VS_API_KEY ?? process.env.VISUALSENTINEL_API_KEY ?? void 0;
29
+ this.baseUrl = (opts.baseUrl ?? process.env.VS_API_BASE ?? DEFAULT_BASE).replace(
30
+ /\/+$/,
31
+ ""
32
+ );
33
+ this.userAgent = opts.userAgent ?? "@visualsentinel/mcp-server";
34
+ }
35
+ hasApiKey() {
36
+ return typeof this.apiKey === "string" && this.apiKey.length > 0;
37
+ }
38
+ async request(method, path, options = {}) {
39
+ const auth = options.auth ?? true;
40
+ if (auth && !this.hasApiKey()) {
41
+ throw new VsApiError(
42
+ "Visual Sentinel API key required. Set VS_API_KEY (https://visualsentinel.com/settings/api-keys).",
43
+ 401,
44
+ null
45
+ );
46
+ }
47
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, this.baseUrl);
48
+ if (options.query) {
49
+ for (const [k, v] of Object.entries(options.query)) {
50
+ if (v !== void 0 && v !== null) url.searchParams.set(k, String(v));
51
+ }
52
+ }
53
+ const headers = {
54
+ Accept: "application/json",
55
+ "User-Agent": this.userAgent
56
+ };
57
+ if (auth && this.apiKey) headers["X-API-Key"] = this.apiKey;
58
+ if (options.body !== void 0) headers["Content-Type"] = "application/json";
59
+ const res = await fetch(url, {
60
+ method,
61
+ headers,
62
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
63
+ });
64
+ const text = await res.text();
65
+ let parsed = null;
66
+ if (text.length > 0) {
67
+ try {
68
+ parsed = JSON.parse(text);
69
+ } catch {
70
+ parsed = text;
71
+ }
72
+ }
73
+ if (!res.ok) {
74
+ throw new VsApiError(
75
+ `Visual Sentinel API ${method} ${path} returned ${res.status}`,
76
+ res.status,
77
+ parsed
78
+ );
79
+ }
80
+ return parsed;
81
+ }
82
+ };
83
+
84
+ // src/tools.ts
85
+ var STR = { type: "string" };
86
+ var INT = { type: "integer" };
87
+ function pickString(args, key) {
88
+ const v = args[key];
89
+ return typeof v === "string" && v.length > 0 ? v : void 0;
90
+ }
91
+ function pickNumber(args, key) {
92
+ const v = args[key];
93
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
94
+ }
95
+ function requireString(args, key) {
96
+ const v = pickString(args, key);
97
+ if (!v) throw new Error(`Argument "${key}" (string) is required.`);
98
+ return v;
99
+ }
100
+ var TOOLS = [
101
+ // -------------------------------------------------------------------------
102
+ // Public tools (no auth)
103
+ // -------------------------------------------------------------------------
104
+ {
105
+ name: "vs_health",
106
+ description: "Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.",
107
+ inputSchema: {
108
+ type: "object",
109
+ properties: {},
110
+ additionalProperties: false
111
+ },
112
+ requiresAuth: false,
113
+ handler: async (_args, client) => client.request("GET", "/api/health", { auth: false })
114
+ },
115
+ {
116
+ name: "vs_dns_check",
117
+ description: "Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel's public DNS lookup tool. No authentication required.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ domain: { ...STR, description: "Domain to resolve, e.g. example.com (without protocol)." },
122
+ recordType: {
123
+ ...STR,
124
+ description: "Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all."
125
+ }
126
+ },
127
+ required: ["domain"],
128
+ additionalProperties: false
129
+ },
130
+ requiresAuth: false,
131
+ handler: async (args, client) => client.request("GET", "/api/tools/dns-check", {
132
+ auth: false,
133
+ query: {
134
+ domain: requireString(args, "domain"),
135
+ recordType: pickString(args, "recordType")
136
+ }
137
+ })
138
+ },
139
+ {
140
+ name: "vs_ssl_check",
141
+ description: "Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.",
142
+ inputSchema: {
143
+ type: "object",
144
+ properties: {
145
+ host: { ...STR, description: "Hostname to inspect, e.g. example.com (without protocol)." },
146
+ port: { ...INT, description: "TCP port. Default 443." }
147
+ },
148
+ required: ["host"],
149
+ additionalProperties: false
150
+ },
151
+ requiresAuth: false,
152
+ handler: async (args, client) => client.request("GET", "/api/tools/ssl-check", {
153
+ auth: false,
154
+ query: { host: requireString(args, "host"), port: pickNumber(args, "port") }
155
+ })
156
+ },
157
+ {
158
+ name: "vs_speed_test",
159
+ description: "Run a one-shot performance check against a URL: TTFB, total load time, transfer size, status code, and redirect chain. No authentication required.",
160
+ inputSchema: {
161
+ type: "object",
162
+ properties: {
163
+ url: { ...STR, description: "Full URL to test, including protocol (https://...)." }
164
+ },
165
+ required: ["url"],
166
+ additionalProperties: false
167
+ },
168
+ requiresAuth: false,
169
+ handler: async (args, client) => client.request("GET", "/api/tools/speed-test", {
170
+ auth: false,
171
+ query: { url: requireString(args, "url") }
172
+ })
173
+ },
174
+ {
175
+ name: "vs_website_check",
176
+ description: "Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.",
177
+ inputSchema: {
178
+ type: "object",
179
+ properties: {
180
+ url: { ...STR, description: "Full URL to test, including protocol (https://...)." }
181
+ },
182
+ required: ["url"],
183
+ additionalProperties: false
184
+ },
185
+ requiresAuth: false,
186
+ handler: async (args, client) => client.request("GET", "/api/tools/website-check", {
187
+ auth: false,
188
+ query: { url: requireString(args, "url") }
189
+ })
190
+ },
191
+ // -------------------------------------------------------------------------
192
+ // Authenticated tools (require VS_API_KEY)
193
+ // -------------------------------------------------------------------------
194
+ {
195
+ name: "vs_monitors_list",
196
+ description: "List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.",
197
+ inputSchema: {
198
+ type: "object",
199
+ properties: {
200
+ status: { ...STR, description: "Filter: UP, DOWN, PAUSED, MAINTENANCE." },
201
+ type: {
202
+ ...STR,
203
+ description: "Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET."
204
+ },
205
+ page: { ...INT, description: "Page number (default 1)." },
206
+ limit: { ...INT, description: "Items per page (default 50, max 200)." }
207
+ },
208
+ additionalProperties: false
209
+ },
210
+ requiresAuth: true,
211
+ handler: async (args, client) => client.request("GET", "/api/monitors", {
212
+ query: {
213
+ status: pickString(args, "status"),
214
+ type: pickString(args, "type"),
215
+ page: pickNumber(args, "page"),
216
+ limit: pickNumber(args, "limit")
217
+ }
218
+ })
219
+ },
220
+ {
221
+ name: "vs_monitors_get",
222
+ description: "Fetch a single monitor by id, including its current status, last check, and configuration.",
223
+ inputSchema: {
224
+ type: "object",
225
+ properties: {
226
+ id: { ...STR, description: "Monitor id (cuid)." }
227
+ },
228
+ required: ["id"],
229
+ additionalProperties: false
230
+ },
231
+ requiresAuth: true,
232
+ handler: async (args, client) => client.request("GET", `/api/monitors/${encodeURIComponent(requireString(args, "id"))}`)
233
+ },
234
+ {
235
+ name: "vs_monitors_create",
236
+ description: "Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.",
237
+ inputSchema: {
238
+ type: "object",
239
+ properties: {
240
+ name: { ...STR, description: "Display name for the monitor." },
241
+ url: { ...STR, description: "Full URL to monitor (or hostname for PING/PORT/DNS/SSL)." },
242
+ type: {
243
+ ...STR,
244
+ description: "Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET."
245
+ },
246
+ interval: { ...INT, description: "Check interval in seconds (60, 300, 600, 1800, 3600)." },
247
+ timeout: { ...INT, description: "Per-check timeout in seconds (default 30)." },
248
+ method: { ...STR, description: "HTTP method for HTTP/HTTPS monitors (default GET)." },
249
+ keyword: { ...STR, description: "Keyword to assert for KEYWORD monitors." },
250
+ port: { ...INT, description: "TCP port for PORT monitors." }
251
+ },
252
+ required: ["name", "url", "type"],
253
+ additionalProperties: false
254
+ },
255
+ requiresAuth: true,
256
+ handler: async (args, client) => client.request("POST", "/api/monitors", {
257
+ body: {
258
+ name: requireString(args, "name"),
259
+ url: requireString(args, "url"),
260
+ type: requireString(args, "type"),
261
+ interval: pickNumber(args, "interval"),
262
+ timeout: pickNumber(args, "timeout"),
263
+ method: pickString(args, "method"),
264
+ keyword: pickString(args, "keyword"),
265
+ port: pickNumber(args, "port")
266
+ }
267
+ })
268
+ },
269
+ {
270
+ name: "vs_monitors_check_now",
271
+ description: "Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.",
272
+ inputSchema: {
273
+ type: "object",
274
+ properties: {
275
+ id: { ...STR, description: "Monitor id." }
276
+ },
277
+ required: ["id"],
278
+ additionalProperties: false
279
+ },
280
+ requiresAuth: true,
281
+ handler: async (args, client) => client.request("POST", `/api/monitors/${encodeURIComponent(requireString(args, "id"))}/check`)
282
+ },
283
+ {
284
+ name: "vs_monitors_uptime",
285
+ description: "Fetch the uptime percentage and outage breakdown for a monitor over a window (default last 30 days).",
286
+ inputSchema: {
287
+ type: "object",
288
+ properties: {
289
+ id: { ...STR, description: "Monitor id." },
290
+ window: {
291
+ ...STR,
292
+ description: "Window: 24h, 7d, 30d, 90d, 365d. Default 30d."
293
+ }
294
+ },
295
+ required: ["id"],
296
+ additionalProperties: false
297
+ },
298
+ requiresAuth: true,
299
+ handler: async (args, client) => client.request("GET", `/api/monitors/${encodeURIComponent(requireString(args, "id"))}/uptime`, {
300
+ query: { window: pickString(args, "window") }
301
+ })
302
+ },
303
+ {
304
+ name: "vs_incidents_list",
305
+ description: "List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.",
306
+ inputSchema: {
307
+ type: "object",
308
+ properties: {
309
+ status: { ...STR, description: "OPEN or RESOLVED." },
310
+ monitorId: { ...STR, description: "Filter to one monitor." },
311
+ page: { ...INT },
312
+ limit: { ...INT, description: "Default 50, max 200." }
313
+ },
314
+ additionalProperties: false
315
+ },
316
+ requiresAuth: true,
317
+ handler: async (args, client) => client.request("GET", "/api/incidents", {
318
+ query: {
319
+ status: pickString(args, "status"),
320
+ monitorId: pickString(args, "monitorId"),
321
+ page: pickNumber(args, "page"),
322
+ limit: pickNumber(args, "limit")
323
+ }
324
+ })
325
+ },
326
+ {
327
+ name: "vs_incidents_get",
328
+ description: "Fetch a single incident with its full check history and root-cause hints.",
329
+ inputSchema: {
330
+ type: "object",
331
+ properties: {
332
+ id: { ...STR, description: "Incident id." }
333
+ },
334
+ required: ["id"],
335
+ additionalProperties: false
336
+ },
337
+ requiresAuth: true,
338
+ handler: async (args, client) => client.request("GET", `/api/incidents/${encodeURIComponent(requireString(args, "id"))}`)
339
+ },
340
+ {
341
+ name: "vs_alerts_list",
342
+ description: "List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.",
343
+ inputSchema: {
344
+ type: "object",
345
+ properties: {
346
+ status: { ...STR, description: "UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED." },
347
+ page: { ...INT },
348
+ limit: { ...INT, description: "Default 50, max 200." }
349
+ },
350
+ additionalProperties: false
351
+ },
352
+ requiresAuth: true,
353
+ handler: async (args, client) => client.request("GET", "/api/alerts", {
354
+ query: {
355
+ status: pickString(args, "status"),
356
+ page: pickNumber(args, "page"),
357
+ limit: pickNumber(args, "limit")
358
+ }
359
+ })
360
+ },
361
+ {
362
+ name: "vs_alerts_acknowledge",
363
+ description: "Acknowledge an alert by id. Acknowledgement is recorded with the calling API key's user.",
364
+ inputSchema: {
365
+ type: "object",
366
+ properties: {
367
+ id: { ...STR, description: "Alert id." },
368
+ note: { ...STR, description: "Optional acknowledgement note (visible in alert history)." }
369
+ },
370
+ required: ["id"],
371
+ additionalProperties: false
372
+ },
373
+ requiresAuth: true,
374
+ handler: async (args, client) => client.request("POST", `/api/alerts/${encodeURIComponent(requireString(args, "id"))}/acknowledge`, {
375
+ body: { note: pickString(args, "note") }
376
+ })
377
+ },
378
+ {
379
+ name: "vs_status_pages_list",
380
+ description: "List public-facing status pages owned by the authenticated organization.",
381
+ inputSchema: {
382
+ type: "object",
383
+ properties: {},
384
+ additionalProperties: false
385
+ },
386
+ requiresAuth: true,
387
+ handler: async (_args, client) => client.request("GET", "/api/status-pages")
388
+ },
389
+ {
390
+ name: "vs_servers_list",
391
+ description: "List servers (origins) registered in the authenticated organization.",
392
+ inputSchema: {
393
+ type: "object",
394
+ properties: {
395
+ page: { ...INT },
396
+ limit: { ...INT, description: "Default 50." }
397
+ },
398
+ additionalProperties: false
399
+ },
400
+ requiresAuth: true,
401
+ handler: async (args, client) => client.request("GET", "/api/servers", {
402
+ query: { page: pickNumber(args, "page"), limit: pickNumber(args, "limit") }
403
+ })
404
+ }
405
+ ];
406
+ function findTool(name) {
407
+ return TOOLS.find((t) => t.name === name);
408
+ }
409
+
410
+ // src/index.ts
411
+ var SERVER_NAME = "visualsentinel";
412
+ var SERVER_VERSION = "0.1.0";
413
+ function toMcpToolDescriptor(tool) {
414
+ return {
415
+ name: tool.name,
416
+ description: tool.description,
417
+ inputSchema: tool.inputSchema
418
+ };
419
+ }
420
+ async function main() {
421
+ const client = new ApiClient();
422
+ const server = new Server(
423
+ { name: SERVER_NAME, version: SERVER_VERSION },
424
+ { capabilities: { tools: {} } }
425
+ );
426
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
427
+ tools: TOOLS.map(toMcpToolDescriptor)
428
+ }));
429
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
430
+ const tool = findTool(req.params.name);
431
+ if (!tool) {
432
+ return {
433
+ isError: true,
434
+ content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }]
435
+ };
436
+ }
437
+ try {
438
+ const result = await tool.handler(req.params.arguments ?? {}, client);
439
+ return {
440
+ content: [
441
+ {
442
+ type: "text",
443
+ text: JSON.stringify(result, null, 2)
444
+ }
445
+ ]
446
+ };
447
+ } catch (err) {
448
+ const message = err instanceof VsApiError ? `Visual Sentinel API error (${err.status}): ${err.message}
449
+ ${typeof err.body === "string" ? err.body : JSON.stringify(err.body, null, 2)}` : err instanceof Error ? err.message : String(err);
450
+ return {
451
+ isError: true,
452
+ content: [{ type: "text", text: message }]
453
+ };
454
+ }
455
+ });
456
+ const transport = new StdioServerTransport();
457
+ await server.connect(transport);
458
+ }
459
+ main().catch((err) => {
460
+ console.error("[visualsentinel-mcp-server] fatal:", err);
461
+ process.exit(1);
462
+ });
463
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/api-client.ts","../src/tools.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 ListToolsRequestSchema,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport { ApiClient, VsApiError } from './api-client.js';\nimport { TOOLS, findTool } from './tools.js';\n\nconst SERVER_NAME = 'visualsentinel';\nconst SERVER_VERSION = '0.1.0';\n\nfunction toMcpToolDescriptor(tool: (typeof TOOLS)[number]) {\n return {\n name: tool.name,\n description: tool.description,\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: {} } },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: TOOLS.map(toMcpToolDescriptor),\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 'Visual Sentinel API key required. Set VS_API_KEY (https://visualsentinel.com/settings/api-keys).',\n 401,\n null,\n );\n }\n\n const url = new URL(path.startsWith('/') ? path : `/${path}`, this.baseUrl);\n if (options.query) {\n for (const [k, v] of Object.entries(options.query)) {\n if (v !== undefined && v !== null) url.searchParams.set(k, String(v));\n }\n }\n\n const headers: Record<string, string> = {\n Accept: 'application/json',\n 'User-Agent': this.userAgent,\n };\n if (auth && this.apiKey) headers['X-API-Key'] = this.apiKey;\n if (options.body !== undefined) headers['Content-Type'] = 'application/json';\n\n const res = await fetch(url, {\n method,\n headers,\n body: options.body !== undefined ? JSON.stringify(options.body) : undefined,\n });\n\n const text = await res.text();\n let parsed: unknown = null;\n if (text.length > 0) {\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = text;\n }\n }\n\n if (!res.ok) {\n throw new VsApiError(\n `Visual Sentinel API ${method} ${path} returned ${res.status}`,\n res.status,\n parsed,\n );\n }\n\n return parsed as T;\n }\n}\n","/**\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 ToolDefinition {\n name: string;\n description: string;\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 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 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 recordType: pickString(args, 'recordType'),\n },\n }),\n },\n\n {\n name: 'vs_ssl_check',\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 host: { ...STR, description: 'Hostname to inspect, e.g. example.com (without protocol).' },\n port: { ...INT, description: 'TCP port. Default 443.' },\n },\n required: ['host'],\n additionalProperties: false,\n },\n requiresAuth: false,\n handler: async (args, client) =>\n client.request('GET', '/api/tools/ssl-check', {\n auth: false,\n query: { host: requireString(args, 'host'), port: pickNumber(args, 'port') },\n }),\n },\n\n {\n name: 'vs_speed_test',\n description:\n 'Run a one-shot performance check against a URL: TTFB, total load time, transfer size, status code, and redirect chain. 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/speed-test', {\n auth: false,\n query: { url: requireString(args, 'url') },\n }),\n },\n\n {\n name: 'vs_website_check',\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 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 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 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 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 description:\n 'Fetch the uptime percentage and outage breakdown for a monitor over a window (default last 30 days).',\n inputSchema: {\n type: 'object',\n properties: {\n id: { ...STR, description: 'Monitor id.' },\n window: {\n ...STR,\n description: 'Window: 24h, 7d, 30d, 90d, 365d. Default 30d.',\n },\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'))}/uptime`, {\n query: { window: pickString(args, 'window') },\n }),\n },\n\n {\n name: 'vs_incidents_list',\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 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 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 description: 'Acknowledge an alert by id. Acknowledgement is recorded with the calling API key\\'s user.',\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 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 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"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@visualsentinel/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "Model Context Protocol server for Visual Sentinel: six-layer website monitoring (uptime, performance, SSL, DNS, visual regression, content change) accessible from Claude Desktop, Claude Code, Cline, Continue, and any MCP-compatible client.",
5
+ "type": "module",
6
+ "bin": {
7
+ "visualsentinel-mcp-server": "dist/index.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "types": "dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE",
15
+ "CHANGELOG.md"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "start": "node dist/index.js",
21
+ "lint": "tsc --noEmit",
22
+ "test": "node --test tests/*.test.mjs",
23
+ "prepublishOnly": "npm run lint && npm run build"
24
+ },
25
+ "keywords": [
26
+ "mcp",
27
+ "model-context-protocol",
28
+ "visual-sentinel",
29
+ "website-monitoring",
30
+ "uptime",
31
+ "ssl",
32
+ "dns",
33
+ "visual-regression",
34
+ "claude",
35
+ "claude-desktop",
36
+ "cline"
37
+ ],
38
+ "author": "Visual Sentinel <support@visualsentinel.com>",
39
+ "license": "MIT",
40
+ "homepage": "https://github.com/VisualSentinel/mcp-server#readme",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://github.com/VisualSentinel/mcp-server.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/VisualSentinel/mcp-server/issues"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "dependencies": {
52
+ "@modelcontextprotocol/sdk": "^1.29.0",
53
+ "zod": "^3.23.0"
54
+ },
55
+ "devDependencies": {
56
+ "@types/node": "^22.0.0",
57
+ "tsup": "^8.0.0",
58
+ "typescript": "^5.4.0"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ }
63
+ }