@davidfuchs/mcp-uptime-kuma 0.7.0 → 0.10.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/README.md CHANGED
@@ -86,16 +86,56 @@ See [Authentication Methods](#authentication-methods) for JWT token and anonymou
86
86
 
87
87
  ## Available Tools
88
88
 
89
+ ### Monitors
90
+
89
91
  | Tool | Purpose |
90
92
  |------|---------|
91
93
  | `getMonitorSummary` | Get a quick overview of all monitors with their current status. Supports filtering. |
92
94
  | `listMonitors` | Get the full list of all monitors with configurations. Supports filtering. |
93
95
  | `listMonitorTypes` | Get all available monitor types supported by Uptime Kuma. |
94
96
  | `getMonitor` | Get detailed configuration for a specific monitor by ID. |
97
+ | `createMonitor` | Create a new monitor (requires name and type at minimum). |
98
+ | `updateMonitor` | Update an existing monitor's configuration. |
99
+ | `deleteMonitor` | Permanently delete a monitor and all its heartbeat history. |
95
100
  | `pauseMonitor` | Pause a monitor to stop performing checks. |
96
101
  | `resumeMonitor` | Resume a paused monitor to restart checks. |
102
+
103
+ ### Heartbeats
104
+
105
+ | Tool | Purpose |
106
+ |------|---------|
97
107
  | `listHeartbeats` | Get status check history for all monitors. |
98
108
  | `getHeartbeats` | Get status check history for a specific monitor. |
109
+
110
+ ### Notifications
111
+
112
+ | Tool | Purpose |
113
+ |------|---------|
114
+ | `listNotifications` | List all configured notification channels (Slack, Discord, email, webhooks, etc.). |
115
+ | `addNotification` | Create a new notification channel. |
116
+ | `updateNotification` | Update an existing notification channel. |
117
+ | `deleteNotification` | Permanently delete a notification channel. |
118
+
119
+ ### Tags
120
+
121
+ | Tool | Purpose |
122
+ |------|---------|
123
+ | `listTags` | List all tags defined in Uptime Kuma. |
124
+ | `addTag` | Create a new tag that can be assigned to monitors. |
125
+ | `deleteTag` | Permanently delete a tag (removes it from all monitors). |
126
+
127
+ ### Maintenance
128
+
129
+ | Tool | Purpose |
130
+ |------|---------|
131
+ | `getMaintenanceWindows` | List all scheduled maintenance windows. |
132
+ | `createMaintenance` | Schedule a new maintenance window. |
133
+
134
+ ### Status Pages & Settings
135
+
136
+ | Tool | Purpose |
137
+ |------|---------|
138
+ | `listStatusPages` | List all configured status pages. |
99
139
  | `getSettings` | Get Uptime Kuma server settings. |
100
140
 
101
141
  ### Filtering
@@ -167,12 +207,19 @@ mcpServers:
167
207
  ```
168
208
 
169
209
  **streamable HTTP transport:**
210
+
211
+ Update the allowed domains to whatever domain you're using in the URL (e.g., `localhost` or `host.docker.internal` for Docker setups):
212
+
170
213
  ```yaml
171
214
  mcpServers:
172
215
  uptime-kuma:
173
216
  type: streamable-http
174
217
  url: "http://mcp-uptime-kuma:3000/mcp"
175
218
  serverInstructions: true
219
+
220
+ mcpSettings:
221
+ allowedDomains:
222
+ - 'mcp-uptime-kuma'
176
223
  ```
177
224
 
178
225
  ## Contributing
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CA2gC9J"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,gBAAgB,CAAC;IAAC,kBAAkB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAs2C9J"}
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { McpError, ErrorCode, SetLevelRequestSchema, LoggingLevelSchema } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { z } from 'zod';
4
4
  import { UptimeKumaClient } from './uptime-kuma-client.js';
5
- import { HeartbeatSchema, MonitorBaseSchema, MonitorSummarySchema, SettingsSchema, NotificationSchema, MaintenanceSchema, StatusPageSchema } from './types/index.js';
5
+ import { HeartbeatSchema, MonitorBaseSchema, MonitorSummarySchema, SettingsSchema, NotificationSchema, MaintenanceSchema, StatusPageSchema, DockerHostSchema } from './types/index.js';
6
6
  import { VERSION } from './version.js';
7
7
  /**
8
8
  * Creates and configures the MCP server with tools, resources, and prompts
@@ -25,13 +25,17 @@ export async function createServer(config) {
25
25
  - Use 'listNotifications' to see notification channels.
26
26
  - Use 'listTags' to see available tags.
27
27
  - Use 'getMaintenanceWindows' to see scheduled maintenance.
28
- - Use 'listStatusPages' to see status page configurations.
28
+ - Use 'listStatusPages' to see status page configurations, or 'getStatusPage' for one page's full details (groups + monitors).
29
+ - Use 'listDockerHosts' to see configured docker daemons (used by docker container monitors).
29
30
 
30
31
  WRITE operations:
31
32
  - Use 'createMonitor' / 'updateMonitor' / 'deleteMonitor' to manage monitors.
32
33
  - Use 'addNotification' / 'updateNotification' / 'deleteNotification' to manage notification channels.
33
34
  - Use 'addTag' / 'deleteTag' to manage tags.
34
35
  - Use 'createMaintenance' to schedule a maintenance window.
36
+ - Use 'addDockerHost' / 'updateDockerHost' / 'deleteDockerHost' to manage docker daemon connections.
37
+ - Use 'testDockerHost' to verify a docker daemon is reachable before saving.
38
+ - Use 'createStatusPage' / 'updateStatusPage' / 'deleteStatusPage' to manage status pages. Creating returns an empty page — follow up with updateStatusPage to set groups and monitors.
35
39
  - Use 'pauseMonitor' / 'resumeMonitor' to temporarily stop/start checks.
36
40
  `,
37
41
  capabilities: {
@@ -436,6 +440,10 @@ export async function createServer(config) {
436
440
  maxredirects: z.coerce.number().optional().describe('Max HTTP redirects (default: 10)'),
437
441
  upsideDown: z.boolean().optional().describe('Invert status — treat up as down'),
438
442
  parent: z.coerce.number().nullable().optional().describe('Parent group monitor ID'),
443
+ docker_container: z.string().optional().describe('Docker container name (required for docker type)'),
444
+ docker_host: z.coerce.number().optional().describe('Docker host ID (required for docker type). Use listDockerHosts to find available IDs.'),
445
+ dns_resolve_server: z.string().optional().describe('DNS server to use for resolution (required for dns type, default: 1.1.1.1)'),
446
+ dns_resolve_type: z.enum(['A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT', 'CAA']).optional().describe('DNS record type to query (required for dns type, default: A)'),
439
447
  },
440
448
  outputSchema: {
441
449
  ok: z.boolean(),
@@ -447,10 +455,18 @@ export async function createServer(config) {
447
455
  throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
448
456
  }
449
457
  try {
450
- const monitorData = {
458
+ const defaults = {
451
459
  notificationIDList: {},
452
460
  accepted_statuscodes: ['200-299'],
453
461
  conditions: [],
462
+ retryInterval: 60,
463
+ };
464
+ if (input.type === 'dns') {
465
+ defaults.dns_resolve_server = '1.1.1.1';
466
+ defaults.dns_resolve_type = 'A';
467
+ }
468
+ const monitorData = {
469
+ ...defaults,
454
470
  ...input,
455
471
  };
456
472
  const response = await client.createMonitor(monitorData);
@@ -492,6 +508,10 @@ export async function createServer(config) {
492
508
  maxredirects: z.coerce.number().optional().describe('Max HTTP redirects'),
493
509
  upsideDown: z.boolean().optional().describe('Invert status'),
494
510
  active: z.boolean().optional().describe('Whether the monitor is active'),
511
+ docker_container: z.string().optional().describe('Docker container name (required for docker type)'),
512
+ docker_host: z.coerce.number().optional().describe('Docker host ID (required for docker type). Use listDockerHosts to find available IDs.'),
513
+ dns_resolve_server: z.string().optional().describe('DNS server to use for resolution (for dns type)'),
514
+ dns_resolve_type: z.enum(['A', 'AAAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SRV', 'TXT', 'CAA']).optional().describe('DNS record type to query (for dns type)'),
495
515
  },
496
516
  outputSchema: {
497
517
  ok: z.boolean(),
@@ -507,7 +527,14 @@ export async function createServer(config) {
507
527
  if (!existing) {
508
528
  throw new Error(`Monitor ${monitorID} not found`);
509
529
  }
510
- const merged = { ...existing, ...rest, id: monitorID };
530
+ // Strip undefined values so existing config is preserved for omitted fields
531
+ const defined = Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined));
532
+ const merged = { ...existing, ...defined, id: monitorID };
533
+ // Ensure retryInterval is valid — Kuma rejects values < 1 on edit even if
534
+ // it stored 0 during creation (pre-existing monitors or older defaults)
535
+ if (!merged.retryInterval || merged.retryInterval < 1) {
536
+ merged.retryInterval = merged.interval || 60;
537
+ }
511
538
  const response = await client.updateMonitor(merged);
512
539
  return {
513
540
  content: [{ type: 'text', text: response.msg || `Monitor ${monitorID} updated successfully` }],
@@ -669,6 +696,156 @@ export async function createServer(config) {
669
696
  throw new McpError(ErrorCode.InternalError, `Failed to delete notification: ${errorMessage}`);
670
697
  }
671
698
  });
699
+ // ─── Docker host tools ───────────────────────────────────────────────────
700
+ server.registerTool('listDockerHosts', {
701
+ title: 'List Docker Hosts',
702
+ description: 'Returns all docker daemon connections configured in Uptime Kuma. These are referenced by docker container monitors via docker_host.',
703
+ inputSchema: {},
704
+ outputSchema: {
705
+ dockerHosts: z.array(DockerHostSchema).describe('Array of docker host configurations'),
706
+ count: z.number(),
707
+ },
708
+ }, async () => {
709
+ if (!isAuthenticated) {
710
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
711
+ }
712
+ try {
713
+ const dockerHosts = client.getDockerHostList();
714
+ return {
715
+ content: [{ type: 'text', text: JSON.stringify(dockerHosts, null, 2) }],
716
+ structuredContent: { dockerHosts, count: dockerHosts.length },
717
+ };
718
+ }
719
+ catch (error) {
720
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
721
+ throw new McpError(ErrorCode.InternalError, `Failed to list docker hosts: ${errorMessage}`);
722
+ }
723
+ });
724
+ server.registerTool('addDockerHost', {
725
+ title: 'Add Docker Host',
726
+ description: 'Creates a new docker daemon connection. For a unix socket use dockerType="socket" and dockerDaemon="/var/run/docker.sock". For a TCP proxy (e.g. tecnativa/docker-socket-proxy) use dockerType="tcp" and dockerDaemon="http://host:2375". Consider calling testDockerHost first to verify reachability.',
727
+ inputSchema: {
728
+ name: z.string().describe('Human-readable name for this docker host'),
729
+ dockerType: z.enum(['socket', 'tcp']).describe('"socket" for a unix socket path, "tcp" for an HTTP/HTTPS URL'),
730
+ dockerDaemon: z.string().describe('Unix socket path (e.g. /var/run/docker.sock) when dockerType=socket, or TCP URL (e.g. http://docker-proxy:2375) when dockerType=tcp'),
731
+ },
732
+ outputSchema: {
733
+ ok: z.boolean(),
734
+ id: z.number().optional(),
735
+ msg: z.string().optional(),
736
+ },
737
+ }, async ({ name, dockerType, dockerDaemon }) => {
738
+ if (!isAuthenticated) {
739
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
740
+ }
741
+ try {
742
+ const response = await client.addDockerHost({ name, dockerType, dockerDaemon });
743
+ return {
744
+ content: [{ type: 'text', text: response.msg || `Docker host created with ID ${response.id}` }],
745
+ structuredContent: { ok: response.ok, id: response.id, msg: response.msg },
746
+ };
747
+ }
748
+ catch (error) {
749
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
750
+ throw new McpError(ErrorCode.InternalError, `Failed to add docker host: ${errorMessage}`);
751
+ }
752
+ });
753
+ server.registerTool('updateDockerHost', {
754
+ title: 'Update Docker Host',
755
+ description: 'Updates an existing docker daemon connection. Use listDockerHosts to find the docker host ID. Only the fields you pass are changed — the others are preserved.',
756
+ inputSchema: {
757
+ dockerHostID: z.coerce.number().int().nonnegative().describe('The ID of the docker host to update'),
758
+ name: z.string().optional().describe('New human-readable name'),
759
+ dockerType: z.enum(['socket', 'tcp']).optional().describe('New connection type'),
760
+ dockerDaemon: z.string().optional().describe('New socket path or TCP URL'),
761
+ },
762
+ outputSchema: {
763
+ ok: z.boolean(),
764
+ id: z.number().optional(),
765
+ msg: z.string().optional(),
766
+ },
767
+ }, async ({ dockerHostID, name, dockerType, dockerDaemon }) => {
768
+ if (!isAuthenticated) {
769
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
770
+ }
771
+ try {
772
+ // Merge new values onto the current record so callers can omit unchanged fields.
773
+ // Uptime Kuma's addDockerHost handler overwrites every column it receives, so we
774
+ // need to send the full set to avoid clobbering existing values with undefined.
775
+ const existing = client.getDockerHostList().find(h => h.id === dockerHostID);
776
+ if (!existing) {
777
+ throw new Error(`Docker host ${dockerHostID} not found — call listDockerHosts to see available IDs`);
778
+ }
779
+ const merged = {
780
+ name: name ?? existing.name,
781
+ dockerType: dockerType ?? existing.dockerType,
782
+ dockerDaemon: dockerDaemon ?? existing.dockerDaemon,
783
+ };
784
+ const response = await client.addDockerHost(merged, dockerHostID);
785
+ return {
786
+ content: [{ type: 'text', text: response.msg || `Docker host ${dockerHostID} updated` }],
787
+ structuredContent: { ok: response.ok, id: response.id, msg: response.msg },
788
+ };
789
+ }
790
+ catch (error) {
791
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
792
+ throw new McpError(ErrorCode.InternalError, `Failed to update docker host: ${errorMessage}`);
793
+ }
794
+ });
795
+ server.registerTool('deleteDockerHost', {
796
+ title: 'Delete Docker Host',
797
+ description: 'Permanently deletes a docker daemon connection. Any monitors referencing it will have their docker_host cleared by Uptime Kuma (the monitors themselves are not deleted).',
798
+ inputSchema: {
799
+ dockerHostID: z.coerce.number().int().nonnegative().describe('The ID of the docker host to delete'),
800
+ },
801
+ outputSchema: {
802
+ ok: z.boolean(),
803
+ msg: z.string().optional(),
804
+ },
805
+ }, async ({ dockerHostID }) => {
806
+ if (!isAuthenticated) {
807
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
808
+ }
809
+ try {
810
+ const response = await client.deleteDockerHost(dockerHostID);
811
+ return {
812
+ content: [{ type: 'text', text: response.msg || `Docker host ${dockerHostID} deleted` }],
813
+ structuredContent: { ok: response.ok, msg: response.msg },
814
+ };
815
+ }
816
+ catch (error) {
817
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
818
+ throw new McpError(ErrorCode.InternalError, `Failed to delete docker host: ${errorMessage}`);
819
+ }
820
+ });
821
+ server.registerTool('testDockerHost', {
822
+ title: 'Test Docker Host',
823
+ description: 'Tests connectivity to a docker daemon without persisting it. On success the message includes the number of containers. Use this before addDockerHost to avoid saving a broken configuration.',
824
+ inputSchema: {
825
+ name: z.string().describe('Display name (used only in the test request)'),
826
+ dockerType: z.enum(['socket', 'tcp']).describe('"socket" for a unix socket path, "tcp" for an HTTP/HTTPS URL'),
827
+ dockerDaemon: z.string().describe('Unix socket path or TCP URL to probe'),
828
+ },
829
+ outputSchema: {
830
+ ok: z.boolean(),
831
+ msg: z.string().optional(),
832
+ },
833
+ }, async ({ name, dockerType, dockerDaemon }) => {
834
+ if (!isAuthenticated) {
835
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
836
+ }
837
+ try {
838
+ const response = await client.testDockerHost({ name, dockerType, dockerDaemon });
839
+ return {
840
+ content: [{ type: 'text', text: response.msg || (response.ok ? 'Docker host reachable' : 'Docker host unreachable') }],
841
+ structuredContent: { ok: response.ok, msg: response.msg },
842
+ };
843
+ }
844
+ catch (error) {
845
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
846
+ throw new McpError(ErrorCode.InternalError, `Failed to test docker host: ${errorMessage}`);
847
+ }
848
+ });
672
849
  // ─── Tag tools ───────────────────────────────────────────────────────────
673
850
  server.registerTool('listTags', {
674
851
  title: 'List Tags',
@@ -687,7 +864,7 @@ export async function createServer(config) {
687
864
  throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
688
865
  }
689
866
  try {
690
- const tags = client.getTagList();
867
+ const tags = await client.getTagList();
691
868
  return {
692
869
  content: [{ type: 'text', text: JSON.stringify(tags, null, 2) }],
693
870
  structuredContent: { tags, count: tags.length },
@@ -782,7 +959,7 @@ export async function createServer(config) {
782
959
  description: 'Schedules a new maintenance window. During maintenance, affected monitors are suppressed and show MAINTENANCE status instead of DOWN.',
783
960
  inputSchema: {
784
961
  title: z.string().describe('Title of the maintenance window'),
785
- description: z.string().optional().describe('Description or reason for the maintenance'),
962
+ description: z.string().default('').describe('Description or reason for the maintenance'),
786
963
  strategy: z.enum(['single', 'recurring-interval', 'recurring-weekday', 'recurring-day-of-month', 'manual'])
787
964
  .describe('Scheduling strategy: single=one-time, recurring-interval=every N days, recurring-weekday=specific weekdays, recurring-day-of-month=specific dates, manual=manually activated'),
788
965
  active: z.boolean().optional().describe('Whether the window is active (default: true)'),
@@ -843,6 +1020,123 @@ export async function createServer(config) {
843
1020
  throw new McpError(ErrorCode.InternalError, `Failed to list status pages: ${errorMessage}`);
844
1021
  }
845
1022
  });
1023
+ server.registerTool('getStatusPage', {
1024
+ title: 'Get Status Page',
1025
+ description: 'Returns the full configuration of a status page by slug, including the ordered list of groups, the monitors inside each group, and active incidents. Only works for published status pages (fetches the public `/api/status-page/{slug}` endpoint).',
1026
+ inputSchema: {
1027
+ slug: z.string().describe('The status page slug (the URL-safe identifier)'),
1028
+ },
1029
+ outputSchema: {
1030
+ ok: z.boolean(),
1031
+ config: StatusPageSchema.optional(),
1032
+ publicGroupList: z.array(z.record(z.string(), z.unknown())).optional().describe('Ordered groups with their monitorList'),
1033
+ incidents: z.array(z.record(z.string(), z.unknown())).optional().describe('Active incidents on the status page'),
1034
+ msg: z.string().optional(),
1035
+ },
1036
+ }, async ({ slug }) => {
1037
+ if (!isAuthenticated) {
1038
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
1039
+ }
1040
+ try {
1041
+ const response = await client.getStatusPage(slug);
1042
+ return {
1043
+ content: [{ type: 'text', text: JSON.stringify(response, null, 2) }],
1044
+ structuredContent: {
1045
+ ok: response.ok,
1046
+ config: response.config,
1047
+ publicGroupList: response.publicGroupList,
1048
+ incidents: response.incidents,
1049
+ msg: response.msg,
1050
+ },
1051
+ };
1052
+ }
1053
+ catch (error) {
1054
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1055
+ throw new McpError(ErrorCode.InternalError, `Failed to get status page: ${errorMessage}`);
1056
+ }
1057
+ });
1058
+ server.registerTool('createStatusPage', {
1059
+ title: 'Create Status Page',
1060
+ description: 'Creates a new (empty) status page with the given title and slug. After creating, call updateStatusPage to set the description, theme, groups, and monitors. Slug must be lowercase letters, digits, and dashes only.',
1061
+ inputSchema: {
1062
+ title: z.string().describe('Display title of the status page'),
1063
+ slug: z.string().regex(/^[a-z0-9-]+$/).describe('URL slug (lowercase letters, digits, and dashes only)'),
1064
+ },
1065
+ outputSchema: {
1066
+ ok: z.boolean(),
1067
+ msg: z.string().optional(),
1068
+ },
1069
+ }, async ({ title, slug }) => {
1070
+ if (!isAuthenticated) {
1071
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
1072
+ }
1073
+ try {
1074
+ const response = await client.createStatusPage(title, slug);
1075
+ return {
1076
+ content: [{ type: 'text', text: response.msg || `Status page ${slug} created` }],
1077
+ structuredContent: { ok: response.ok, msg: response.msg },
1078
+ };
1079
+ }
1080
+ catch (error) {
1081
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1082
+ throw new McpError(ErrorCode.InternalError, `Failed to create status page: ${errorMessage}`);
1083
+ }
1084
+ });
1085
+ server.registerTool('updateStatusPage', {
1086
+ title: 'Update Status Page',
1087
+ description: 'Updates an existing status page. Pass the full config (title, description, theme, published, etc.) and the full publicGroupList — both are replaced wholesale. Each group has a name, weight, and monitorList of [{id}]. Use getStatusPage first to read current state before modifying.',
1088
+ inputSchema: {
1089
+ slug: z.string().describe('The status page slug (immutable identifier)'),
1090
+ config: z.record(z.string(), z.unknown()).describe('Full status page config (title, description, theme, published, showTags, showPoweredBy, domainNameList, customCSS, footerText, icon, etc.)'),
1091
+ publicGroupList: z.array(z.record(z.string(), z.unknown())).optional().describe('Ordered groups. Each: {name, weight, monitorList: [{id}]}. Defaults to empty list.'),
1092
+ imgDataUrl: z.string().optional().describe('Icon as data URL. Omit or pass empty string to keep existing.'),
1093
+ },
1094
+ outputSchema: {
1095
+ ok: z.boolean(),
1096
+ msg: z.string().optional(),
1097
+ },
1098
+ }, async ({ slug, config, publicGroupList, imgDataUrl }) => {
1099
+ if (!isAuthenticated) {
1100
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
1101
+ }
1102
+ try {
1103
+ const response = await client.updateStatusPage(slug, config, publicGroupList ?? [], imgDataUrl ?? '');
1104
+ return {
1105
+ content: [{ type: 'text', text: response.msg || `Status page ${slug} updated` }],
1106
+ structuredContent: { ok: response.ok, msg: response.msg },
1107
+ };
1108
+ }
1109
+ catch (error) {
1110
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1111
+ throw new McpError(ErrorCode.InternalError, `Failed to update status page: ${errorMessage}`);
1112
+ }
1113
+ });
1114
+ server.registerTool('deleteStatusPage', {
1115
+ title: 'Delete Status Page',
1116
+ description: 'Permanently deletes a status page by slug. The status page URL will no longer be accessible.',
1117
+ inputSchema: {
1118
+ slug: z.string().describe('The status page slug to delete'),
1119
+ },
1120
+ outputSchema: {
1121
+ ok: z.boolean(),
1122
+ msg: z.string().optional(),
1123
+ },
1124
+ }, async ({ slug }) => {
1125
+ if (!isAuthenticated) {
1126
+ throw new McpError(ErrorCode.InternalError, 'Not authenticated with Uptime Kuma');
1127
+ }
1128
+ try {
1129
+ const response = await client.deleteStatusPage(slug);
1130
+ return {
1131
+ content: [{ type: 'text', text: response.msg || `Status page ${slug} deleted` }],
1132
+ structuredContent: { ok: response.ok, msg: response.msg },
1133
+ };
1134
+ }
1135
+ catch (error) {
1136
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
1137
+ throw new McpError(ErrorCode.InternalError, `Failed to delete status page: ${errorMessage}`);
1138
+ }
1139
+ });
846
1140
  // Clean up on server shutdown
847
1141
  process.on('SIGINT', () => {
848
1142
  client.disconnect();