@visualsentinel/mcp-server 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,635 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/tools.ts
4
+ var STR = { type: "string" };
5
+ var INT = { type: "integer" };
6
+ function pickString(args, key) {
7
+ const v = args[key];
8
+ return typeof v === "string" && v.length > 0 ? v : void 0;
9
+ }
10
+ function pickNumber(args, key) {
11
+ const v = args[key];
12
+ return typeof v === "number" && Number.isFinite(v) ? v : void 0;
13
+ }
14
+ function requireString(args, key) {
15
+ const v = pickString(args, key);
16
+ if (!v) throw new Error(`Argument "${key}" (string) is required.`);
17
+ return v;
18
+ }
19
+ var TOOLS = [
20
+ // -------------------------------------------------------------------------
21
+ // Public tools (no auth)
22
+ // -------------------------------------------------------------------------
23
+ {
24
+ name: "vs_health",
25
+ title: "Check Visual Sentinel service health",
26
+ annotations: {
27
+ title: "Check Visual Sentinel service health",
28
+ readOnlyHint: true,
29
+ destructiveHint: false,
30
+ idempotentHint: true,
31
+ openWorldHint: true
32
+ },
33
+ description: "Check whether Visual Sentinel itself is up. Returns the service health status. No authentication required.",
34
+ inputSchema: {
35
+ type: "object",
36
+ properties: {},
37
+ additionalProperties: false
38
+ },
39
+ requiresAuth: false,
40
+ handler: async (_args, client) => client.request("GET", "/api/health", { auth: false })
41
+ },
42
+ {
43
+ name: "vs_dns_check",
44
+ title: "Look up DNS records",
45
+ annotations: {
46
+ title: "Look up DNS records",
47
+ readOnlyHint: true,
48
+ destructiveHint: false,
49
+ idempotentHint: true,
50
+ openWorldHint: true
51
+ },
52
+ description: "Resolve DNS records (A, AAAA, MX, NS, TXT, CNAME) for a domain using Visual Sentinel's public DNS lookup tool. No authentication required.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: {
56
+ domain: { ...STR, description: "Domain to resolve, e.g. example.com (without protocol)." },
57
+ recordType: {
58
+ ...STR,
59
+ description: "Optional: limit to one record type. One of: A, AAAA, MX, NS, TXT, CNAME, SOA. Default: all."
60
+ }
61
+ },
62
+ required: ["domain"],
63
+ additionalProperties: false
64
+ },
65
+ requiresAuth: false,
66
+ handler: async (args, client) => client.request("GET", "/api/tools/dns-check", {
67
+ auth: false,
68
+ query: {
69
+ domain: requireString(args, "domain"),
70
+ // The API reads ?type= (not recordType).
71
+ type: pickString(args, "recordType")
72
+ }
73
+ })
74
+ },
75
+ {
76
+ name: "vs_ssl_check",
77
+ title: "Inspect an SSL certificate",
78
+ annotations: {
79
+ title: "Inspect an SSL certificate",
80
+ readOnlyHint: true,
81
+ destructiveHint: false,
82
+ idempotentHint: true,
83
+ openWorldHint: true
84
+ },
85
+ description: "Inspect a TLS/SSL certificate for a hostname: issuer, subject, validity dates, SAN list, key algorithm, and certificate chain. No authentication required.",
86
+ inputSchema: {
87
+ type: "object",
88
+ properties: {
89
+ domain: { ...STR, description: "Hostname to inspect, e.g. example.com (without protocol)." }
90
+ },
91
+ required: ["domain"],
92
+ additionalProperties: false
93
+ },
94
+ requiresAuth: false,
95
+ handler: async (args, client) => client.request("GET", "/api/tools/ssl-check", {
96
+ auth: false,
97
+ // The API reads ?domain=. `host` stays as an input alias for 0.1.x callers.
98
+ query: { domain: pickString(args, "domain") ?? requireString(args, "host") }
99
+ })
100
+ },
101
+ {
102
+ name: "vs_speed_test",
103
+ title: "Run a one-shot speed test",
104
+ annotations: {
105
+ title: "Run a one-shot speed test",
106
+ readOnlyHint: true,
107
+ destructiveHint: false,
108
+ idempotentHint: true,
109
+ openWorldHint: true
110
+ },
111
+ description: "Run a one-shot Lighthouse lab test on a URL. Returns the performance score (0 to 100) plus Largest Contentful Paint, First Contentful Paint, Cumulative Layout Shift, Time to First Byte, Total Blocking Time and Speed Index (times in milliseconds). No account needed.",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: {
115
+ url: { ...STR, description: "Full URL to test, including protocol (https://...)." }
116
+ },
117
+ required: ["url"],
118
+ additionalProperties: false
119
+ },
120
+ requiresAuth: false,
121
+ handler: async (args, client) => client.request("GET", "/api/tools/speed-test", {
122
+ auth: false,
123
+ query: { url: requireString(args, "url") }
124
+ })
125
+ },
126
+ {
127
+ name: "vs_website_check",
128
+ title: "Check a website",
129
+ annotations: {
130
+ title: "Check a website",
131
+ readOnlyHint: true,
132
+ destructiveHint: false,
133
+ idempotentHint: true,
134
+ openWorldHint: true
135
+ },
136
+ description: "Quick health check for a URL: HTTP status, response time, server header, content snippet, and basic SSL state. No authentication required.",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ url: { ...STR, description: "Full URL to test, including protocol (https://...)." }
141
+ },
142
+ required: ["url"],
143
+ additionalProperties: false
144
+ },
145
+ requiresAuth: false,
146
+ handler: async (args, client) => client.request("GET", "/api/tools/website-check", {
147
+ auth: false,
148
+ query: { url: requireString(args, "url") }
149
+ })
150
+ },
151
+ // -------------------------------------------------------------------------
152
+ // Authenticated tools (require VS_API_KEY)
153
+ // -------------------------------------------------------------------------
154
+ {
155
+ name: "vs_monitors_list",
156
+ title: "List monitors",
157
+ annotations: {
158
+ title: "List monitors",
159
+ readOnlyHint: true,
160
+ destructiveHint: false,
161
+ idempotentHint: true,
162
+ openWorldHint: false
163
+ },
164
+ description: "List monitors in the authenticated organization. Optional filters narrow by status, type, or paginate.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ status: { ...STR, description: "Filter: UP, DOWN, PAUSED, MAINTENANCE." },
169
+ type: {
170
+ ...STR,
171
+ description: "Filter by monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET."
172
+ },
173
+ page: { ...INT, description: "Page number (default 1)." },
174
+ limit: { ...INT, description: "Items per page (default 50, max 200)." }
175
+ },
176
+ additionalProperties: false
177
+ },
178
+ requiresAuth: true,
179
+ handler: async (args, client) => client.request("GET", "/api/monitors", {
180
+ query: {
181
+ status: pickString(args, "status"),
182
+ type: pickString(args, "type"),
183
+ page: pickNumber(args, "page"),
184
+ limit: pickNumber(args, "limit")
185
+ }
186
+ })
187
+ },
188
+ {
189
+ name: "vs_monitors_get",
190
+ title: "Get a monitor",
191
+ annotations: {
192
+ title: "Get a monitor",
193
+ readOnlyHint: true,
194
+ destructiveHint: false,
195
+ idempotentHint: true,
196
+ openWorldHint: false
197
+ },
198
+ description: "Fetch a single monitor by id, including its current status, last check, and configuration.",
199
+ inputSchema: {
200
+ type: "object",
201
+ properties: {
202
+ id: { ...STR, description: "Monitor id (cuid)." }
203
+ },
204
+ required: ["id"],
205
+ additionalProperties: false
206
+ },
207
+ requiresAuth: true,
208
+ handler: async (args, client) => client.request("GET", `/api/monitors/${encodeURIComponent(requireString(args, "id"))}`)
209
+ },
210
+ {
211
+ name: "vs_monitors_create",
212
+ title: "Create a monitor",
213
+ annotations: {
214
+ title: "Create a monitor",
215
+ readOnlyHint: false,
216
+ destructiveHint: false,
217
+ idempotentHint: false,
218
+ openWorldHint: false
219
+ },
220
+ description: "Create a new monitor. Provide at least name, url, and type. Returns the created monitor including its assigned id.",
221
+ inputSchema: {
222
+ type: "object",
223
+ properties: {
224
+ name: { ...STR, description: "Display name for the monitor." },
225
+ url: { ...STR, description: "Full URL to monitor (or hostname for PING/PORT/DNS/SSL)." },
226
+ type: {
227
+ ...STR,
228
+ description: "Monitor type: HTTP, HTTPS, KEYWORD, PING, PORT, DNS, SSL, VISUAL, CONTENT, API, GRAPHQL, WEBSOCKET."
229
+ },
230
+ interval: { ...INT, description: "Check interval in seconds (60, 300, 600, 1800, 3600)." },
231
+ timeout: { ...INT, description: "Per-check timeout in seconds (default 30)." },
232
+ method: { ...STR, description: "HTTP method for HTTP/HTTPS monitors (default GET)." },
233
+ keyword: { ...STR, description: "Keyword to assert for KEYWORD monitors." },
234
+ port: { ...INT, description: "TCP port for PORT monitors." }
235
+ },
236
+ required: ["name", "url", "type"],
237
+ additionalProperties: false
238
+ },
239
+ requiresAuth: true,
240
+ handler: async (args, client) => client.request("POST", "/api/monitors", {
241
+ body: {
242
+ name: requireString(args, "name"),
243
+ url: requireString(args, "url"),
244
+ type: requireString(args, "type"),
245
+ interval: pickNumber(args, "interval"),
246
+ timeout: pickNumber(args, "timeout"),
247
+ method: pickString(args, "method"),
248
+ keyword: pickString(args, "keyword"),
249
+ port: pickNumber(args, "port")
250
+ }
251
+ })
252
+ },
253
+ {
254
+ name: "vs_monitors_check_now",
255
+ title: "Run a monitor check now",
256
+ annotations: {
257
+ title: "Run a monitor check now",
258
+ readOnlyHint: false,
259
+ destructiveHint: false,
260
+ idempotentHint: false,
261
+ openWorldHint: false
262
+ },
263
+ description: "Trigger an immediate check for a monitor (in addition to its scheduled cadence). Returns the freshly-collected check result.",
264
+ inputSchema: {
265
+ type: "object",
266
+ properties: {
267
+ id: { ...STR, description: "Monitor id." }
268
+ },
269
+ required: ["id"],
270
+ additionalProperties: false
271
+ },
272
+ requiresAuth: true,
273
+ handler: async (args, client) => client.request("POST", `/api/monitors/${encodeURIComponent(requireString(args, "id"))}/check`)
274
+ },
275
+ {
276
+ name: "vs_monitors_uptime",
277
+ title: "Get monitor uptime",
278
+ annotations: {
279
+ title: "Get monitor uptime",
280
+ readOnlyHint: true,
281
+ destructiveHint: false,
282
+ idempotentHint: true,
283
+ openWorldHint: false
284
+ },
285
+ description: "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.",
286
+ inputSchema: {
287
+ type: "object",
288
+ properties: {
289
+ id: { ...STR, description: "Monitor id." },
290
+ window: {
291
+ ...STR,
292
+ description: "Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied."
293
+ },
294
+ from: {
295
+ ...STR,
296
+ description: "ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`."
297
+ },
298
+ to: {
299
+ ...STR,
300
+ description: "ISO 8601 end of a custom range. Must be supplied with `from`."
301
+ },
302
+ all: {
303
+ type: "boolean",
304
+ description: "Return the full available history from the earliest stored data. Takes precedence over from/to and window."
305
+ }
306
+ },
307
+ required: ["id"],
308
+ additionalProperties: false
309
+ },
310
+ requiresAuth: true,
311
+ handler: async (args, client) => {
312
+ const from = pickString(args, "from");
313
+ const to = pickString(args, "to");
314
+ const query = args["all"] === true ? { all: true } : from && to ? { from, to } : { period: pickString(args, "window") ?? "30d" };
315
+ return client.request(
316
+ "GET",
317
+ `/api/monitors/${encodeURIComponent(requireString(args, "id"))}/uptime`,
318
+ { query }
319
+ );
320
+ }
321
+ },
322
+ {
323
+ name: "vs_monitors_metrics_bulk",
324
+ title: "Get bulk monitor metrics",
325
+ annotations: {
326
+ title: "Get bulk monitor metrics",
327
+ readOnlyHint: true,
328
+ destructiveHint: false,
329
+ idempotentHint: true,
330
+ openWorldHint: false
331
+ },
332
+ description: "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.",
333
+ inputSchema: {
334
+ type: "object",
335
+ properties: {
336
+ monitorIds: {
337
+ type: "array",
338
+ items: { type: "string" },
339
+ description: "List of monitor IDs to fetch metrics for (max 100)."
340
+ },
341
+ metric: {
342
+ ...STR,
343
+ description: 'Metric to return: "uptime" or "response-time".'
344
+ },
345
+ window: {
346
+ ...STR,
347
+ description: "Preset window: 1h, 6h, 24h, 7d, 30d, 90d, 365d. Default 30d. Ignored when from+to or all are supplied."
348
+ },
349
+ from: {
350
+ ...STR,
351
+ description: "ISO 8601 start of a custom range (e.g. 2026-01-01T00:00:00Z). Must be supplied with `to`."
352
+ },
353
+ to: {
354
+ ...STR,
355
+ description: "ISO 8601 end of a custom range. Must be supplied with `from`."
356
+ },
357
+ all: {
358
+ type: "boolean",
359
+ description: "Return the full available history from the earliest stored data. Takes precedence over from/to and window."
360
+ }
361
+ },
362
+ required: ["monitorIds", "metric"],
363
+ additionalProperties: false
364
+ },
365
+ requiresAuth: true,
366
+ handler: async (args, client) => {
367
+ const monitorIds = args["monitorIds"];
368
+ if (!Array.isArray(monitorIds) || monitorIds.length === 0) {
369
+ throw new Error('Argument "monitorIds" must be a non-empty array.');
370
+ }
371
+ const from = pickString(args, "from");
372
+ const to = pickString(args, "to");
373
+ const body = {
374
+ monitorIds,
375
+ metric: requireString(args, "metric")
376
+ };
377
+ if (args["all"] === true) {
378
+ body["all"] = true;
379
+ } else if (from && to) {
380
+ body["from"] = from;
381
+ body["to"] = to;
382
+ } else {
383
+ body["period"] = pickString(args, "window") ?? "30d";
384
+ }
385
+ return client.request("POST", "/api/monitors/metrics", { body });
386
+ }
387
+ },
388
+ {
389
+ name: "vs_incidents_list",
390
+ title: "List incidents",
391
+ annotations: {
392
+ title: "List incidents",
393
+ readOnlyHint: true,
394
+ destructiveHint: false,
395
+ idempotentHint: true,
396
+ openWorldHint: false
397
+ },
398
+ description: "List incidents for the authenticated organization. Filter by status (OPEN/RESOLVED), monitor id, or paginate.",
399
+ inputSchema: {
400
+ type: "object",
401
+ properties: {
402
+ status: { ...STR, description: "OPEN or RESOLVED." },
403
+ monitorId: { ...STR, description: "Filter to one monitor." },
404
+ page: { ...INT },
405
+ limit: { ...INT, description: "Default 50, max 200." }
406
+ },
407
+ additionalProperties: false
408
+ },
409
+ requiresAuth: true,
410
+ handler: async (args, client) => client.request("GET", "/api/incidents", {
411
+ query: {
412
+ status: pickString(args, "status"),
413
+ monitorId: pickString(args, "monitorId"),
414
+ page: pickNumber(args, "page"),
415
+ limit: pickNumber(args, "limit")
416
+ }
417
+ })
418
+ },
419
+ {
420
+ name: "vs_incidents_get",
421
+ title: "Get an incident",
422
+ annotations: {
423
+ title: "Get an incident",
424
+ readOnlyHint: true,
425
+ destructiveHint: false,
426
+ idempotentHint: true,
427
+ openWorldHint: false
428
+ },
429
+ description: "Fetch a single incident with its full check history and root-cause hints.",
430
+ inputSchema: {
431
+ type: "object",
432
+ properties: {
433
+ id: { ...STR, description: "Incident id." }
434
+ },
435
+ required: ["id"],
436
+ additionalProperties: false
437
+ },
438
+ requiresAuth: true,
439
+ handler: async (args, client) => client.request("GET", `/api/incidents/${encodeURIComponent(requireString(args, "id"))}`)
440
+ },
441
+ {
442
+ name: "vs_alerts_list",
443
+ title: "List alerts",
444
+ annotations: {
445
+ title: "List alerts",
446
+ readOnlyHint: true,
447
+ destructiveHint: false,
448
+ idempotentHint: true,
449
+ openWorldHint: false
450
+ },
451
+ description: "List alerts for the authenticated organization. Filter by status (UNACKNOWLEDGED/ACKNOWLEDGED/RESOLVED) or paginate.",
452
+ inputSchema: {
453
+ type: "object",
454
+ properties: {
455
+ status: { ...STR, description: "UNACKNOWLEDGED, ACKNOWLEDGED, or RESOLVED." },
456
+ page: { ...INT },
457
+ limit: { ...INT, description: "Default 50, max 200." }
458
+ },
459
+ additionalProperties: false
460
+ },
461
+ requiresAuth: true,
462
+ handler: async (args, client) => client.request("GET", "/api/alerts", {
463
+ query: {
464
+ status: pickString(args, "status"),
465
+ page: pickNumber(args, "page"),
466
+ limit: pickNumber(args, "limit")
467
+ }
468
+ })
469
+ },
470
+ {
471
+ name: "vs_alerts_acknowledge",
472
+ title: "Acknowledge an alert",
473
+ annotations: {
474
+ title: "Acknowledge an alert",
475
+ readOnlyHint: false,
476
+ destructiveHint: false,
477
+ idempotentHint: true,
478
+ openWorldHint: false
479
+ },
480
+ description: "Acknowledge an alert by id. The acknowledgement is recorded against the account that authorised this connection.",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {
484
+ id: { ...STR, description: "Alert id." },
485
+ note: { ...STR, description: "Optional acknowledgement note (visible in alert history)." }
486
+ },
487
+ required: ["id"],
488
+ additionalProperties: false
489
+ },
490
+ requiresAuth: true,
491
+ handler: async (args, client) => client.request("POST", `/api/alerts/${encodeURIComponent(requireString(args, "id"))}/acknowledge`, {
492
+ body: { note: pickString(args, "note") }
493
+ })
494
+ },
495
+ {
496
+ name: "vs_status_pages_list",
497
+ title: "List status pages",
498
+ annotations: {
499
+ title: "List status pages",
500
+ readOnlyHint: true,
501
+ destructiveHint: false,
502
+ idempotentHint: true,
503
+ openWorldHint: false
504
+ },
505
+ description: "List public-facing status pages owned by the authenticated organization.",
506
+ inputSchema: {
507
+ type: "object",
508
+ properties: {},
509
+ additionalProperties: false
510
+ },
511
+ requiresAuth: true,
512
+ handler: async (_args, client) => client.request("GET", "/api/status-pages")
513
+ },
514
+ {
515
+ name: "vs_servers_list",
516
+ title: "List servers",
517
+ annotations: {
518
+ title: "List servers",
519
+ readOnlyHint: true,
520
+ destructiveHint: false,
521
+ idempotentHint: true,
522
+ openWorldHint: false
523
+ },
524
+ description: "List servers (origins) registered in the authenticated organization.",
525
+ inputSchema: {
526
+ type: "object",
527
+ properties: {
528
+ page: { ...INT },
529
+ limit: { ...INT, description: "Default 50." }
530
+ },
531
+ additionalProperties: false
532
+ },
533
+ requiresAuth: true,
534
+ handler: async (args, client) => client.request("GET", "/api/servers", {
535
+ query: { page: pickNumber(args, "page"), limit: pickNumber(args, "limit") }
536
+ })
537
+ }
538
+ ];
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
+
546
+ // src/resources.ts
547
+ var PLANS_AS_OF = "2026-11-09";
548
+ var PLANS = `# Visual Sentinel plans and limits (as of ${PLANS_AS_OF})
549
+
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.
553
+
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 |
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.
562
+ - The five public tools (vs_health, vs_dns_check, vs_ssl_check, vs_speed_test, vs_website_check) need no account.
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.
566
+ `;
567
+ var HELP = `# Using the Visual Sentinel MCP server
568
+
569
+ Visual Sentinel catches pages that look broken even when the site is up. It watches uptime, performance, SSL certificates, DNS, visual changes and content changes.
570
+
571
+ ## Start without an account
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.
573
+
574
+ ## Connect your account
575
+ 1. Get an API key (${API_KEY_PLANS_SHORT}): https://visualsentinel.com/settings/api-keys
576
+ 2. Set it as VS_API_KEY (or VISUALSENTINEL_API_KEY) in your MCP client's environment for this server.
577
+ 3. Restart the client. Monitor, uptime, incident, alert, status-page and server tools now work.
578
+
579
+ ## Ready-made prompts
580
+ check_website_health, investigate_broken_site, pre_launch_checklist and triage_open_incidents.
581
+
582
+ ## Links
583
+ - Docs and install: https://visualsentinel.com/mcp
584
+ - API reference: https://visualsentinel.com/docs
585
+ - Pricing: https://visualsentinel.com/pricing
586
+ - Source: https://github.com/VisualSentinel/mcp-server
587
+ - Support: https://visualsentinel.com/contact
588
+ `;
589
+ function toolsReference() {
590
+ const rows = TOOLS.map(
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(/\.$/, "")} |`
592
+ );
593
+ return [
594
+ "# Visual Sentinel tools",
595
+ "",
596
+ "| Tool | Needs | Type | What it does |",
597
+ "|---|---|---|---|",
598
+ ...rows,
599
+ ""
600
+ ].join("\n");
601
+ }
602
+ var RESOURCES = [
603
+ {
604
+ uri: "visualsentinel://plans",
605
+ name: "plans",
606
+ title: "Plans and limits",
607
+ description: "Monitor counts, check frequencies and which plans include API keys and the MCP account tools.",
608
+ mimeType: "text/markdown",
609
+ read: () => PLANS
610
+ },
611
+ {
612
+ uri: "visualsentinel://help",
613
+ name: "help",
614
+ title: "Help and setup",
615
+ description: "How to use the server without an account, how to add an API key, and where the docs are.",
616
+ mimeType: "text/markdown",
617
+ read: () => HELP
618
+ },
619
+ {
620
+ uri: "visualsentinel://tools",
621
+ name: "tools",
622
+ title: "Tool reference",
623
+ description: "Every tool, whether it needs an API key, and whether it is read-only.",
624
+ mimeType: "text/markdown",
625
+ read: toolsReference
626
+ }
627
+ ];
628
+ function findResource(uri) {
629
+ return RESOURCES.find((r) => r.uri === uri);
630
+ }
631
+ export {
632
+ RESOURCES,
633
+ findResource
634
+ };
635
+ //# sourceMappingURL=resources.js.map