fettle-mcp 0.1.0 → 0.2.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
@@ -13,6 +13,10 @@ incident or pause checks before a deploy — it calls the same public API you wo
13
13
  "Pause the checkout monitor — we're deploying for the next 20 minutes."
14
14
  ```
15
15
 
16
+ In the [official MCP registry](https://registry.modelcontextprotocol.io) this
17
+ server is `sh.fettle/mcp` — one entry covering both channels, for clients that
18
+ install from the registry rather than from a hand-edited config file.
19
+
16
20
  ## Requirements
17
21
 
18
22
  - Node.js 20 or newer
@@ -92,22 +96,24 @@ claude mcp add fettle --env FETTLE_API_KEY=fettle_your_key_here -- npx -y fettle
92
96
 
93
97
  ## Tools
94
98
 
95
- | Tool | Scope | What it does |
96
- | ---------------------- | ----- | --------------------------------------------------------------------------------------- |
97
- | `list_monitors` | read | List the monitors in the organization the API key belongs to. |
98
- | `get_monitor` | read | Read one monitor's full configuration and current status by id. |
99
- | `create_monitor` | write | Create a monitor that checks a URL, host or port on a schedule. |
100
- | `update_monitor` | write | Change an existing monitor's settings. |
101
- | `pause_monitor` | write | Stop checking a monitor without deleting it — use before planned downtime. |
102
- | `resume_monitor` | write | Resume checks on a paused monitor. |
103
- | `delete_monitor` | write | Soft-delete a monitor: it stops being checked and leaves the monitor list. |
104
- | `check_now` | write | Re-arm a monitor to run on the next scheduler tick instead of waiting out its interval. |
105
- | `get_uptime_summary` | read | Uptime, average response time and incident counts over 24h / 7d / 30d at once. |
106
- | `list_incidents` | read | List incidents, optionally filtered by monitor, status, cause or period. |
107
- | `acknowledge_incident` | write | Acknowledge an open incident so repeat notifications stop. |
108
- | `list_heartbeats` | read | List heartbeat (cron / dead-man's-switch) monitors and when each was last pinged. |
109
- | `create_heartbeat` | write | Create a heartbeat monitor and get the URL a scheduled job should ping. |
110
- | `list_status_pages` | read | List status pages, with each page's slug and whether it is published. |
99
+ | Tool | Scope | What it does |
100
+ | -------------------------- | ----- | --------------------------------------------------------------------------------------- |
101
+ | `list_monitors` | read | List the monitors in the organization the API key belongs to. |
102
+ | `list_tags` | read | List every tag in the organization with how many monitors and heartbeats carry it. |
103
+ | `get_monitor` | read | Read one monitor's full configuration and current status by id. |
104
+ | `create_monitor` | write | Create a monitor that checks a URL, host or port on a schedule. |
105
+ | `update_monitor` | write | Change an existing monitor's settings. |
106
+ | `pause_monitor` | write | Stop checking a monitor without deleting it — use before planned downtime. |
107
+ | `resume_monitor` | write | Resume checks on a paused monitor. |
108
+ | `delete_monitor` | write | Soft-delete a monitor: it stops being checked and leaves the monitor list. |
109
+ | `check_now` | write | Re-arm a monitor to run on the next scheduler tick instead of waiting out its interval. |
110
+ | `bulk_update_monitor_tags` | write | Add and/or remove tags across up to 300 monitors in one call. |
111
+ | `get_uptime_summary` | read | Uptime, average response time and incident counts over 24h / 7d / 30d at once. |
112
+ | `list_incidents` | read | List incidents, optionally filtered by monitor, status, cause or period. |
113
+ | `acknowledge_incident` | write | Acknowledge an open incident so repeat notifications stop. |
114
+ | `list_heartbeats` | read | List heartbeat (cron / dead-man's-switch) monitors and when each was last pinged. |
115
+ | `create_heartbeat` | write | Create a heartbeat monitor and get the URL a scheduled job should ping. |
116
+ | `list_status_pages` | read | List status pages, with each page's slug and whether it is published. |
111
117
 
112
118
  The list is not written by hand. It is generated in the Fettle backend from one
113
119
  registry and served at
@@ -121,6 +127,28 @@ If your client supports remote MCP servers over HTTP, you do not need this
121
127
  package at all — point it at `https://api.fettle.sh/mcp` with the same API key
122
128
  as a Bearer token. Same tools, same behaviour, nothing to install or update.
123
129
 
130
+ Claude Code adds the remote channel from the command line — a different flag
131
+ set from the stdio one above, with the key as a header rather than an
132
+ environment variable:
133
+
134
+ ```bash
135
+ claude mcp add fettle --transport http https://api.fettle.sh/mcp \
136
+ --header "Authorization: Bearer fettle_your_key_here"
137
+ ```
138
+
139
+ Clients configured through a JSON file take the same two fields:
140
+
141
+ ```json
142
+ {
143
+ "mcpServers": {
144
+ "fettle": {
145
+ "url": "https://api.fettle.sh/mcp",
146
+ "headers": { "Authorization": "Bearer fettle_your_key_here" }
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
124
152
  Use this package when your client speaks only stdio, or when you would rather
125
153
  the credential stayed in a local config file than in a client's cloud
126
154
  configuration.
@@ -164,7 +192,12 @@ the contract there, then re-vendor it with
164
192
  `pnpm run contract:check -- --write` and commit the result. No source file in
165
193
  this package describes a tool.
166
194
 
167
- Releasing is documented in [NPM-SETUP.md](./NPM-SETUP.md).
195
+ `server.json` is the entry the [official MCP registry](https://registry.modelcontextprotocol.io)
196
+ serves, and it repeats the version — a test fails if it stops matching
197
+ `package.json`, so bump both together.
198
+
199
+ Releasing is documented in [NPM-SETUP.md](./NPM-SETUP.md) (npm) and
200
+ [REGISTRY.md](./REGISTRY.md) (the MCP registry).
168
201
 
169
202
  ## License
170
203
 
@@ -4,7 +4,7 @@
4
4
  {
5
5
  "name": "list_monitors",
6
6
  "title": "List monitors",
7
- "description": "List the monitors in the organization the API key belongs to. Use this first to find a monitor's id before any other monitor tool.",
7
+ "description": "List the monitors in the organization the API key belongs to. Use this first to find a monitor's id before any other monitor tool. Pass tag one or more times to narrow the list to monitors carrying every tag given (AND, not OR).",
8
8
  "scope": "read",
9
9
  "input_schema": {
10
10
  "type": "object",
@@ -31,6 +31,14 @@
31
31
  "oldest"
32
32
  ],
33
33
  "type": "string"
34
+ },
35
+ "tag": {
36
+ "items": {
37
+ "maxLength": 50,
38
+ "type": "string"
39
+ },
40
+ "maxItems": 10,
41
+ "type": "array"
34
42
  }
35
43
  },
36
44
  "required": [],
@@ -44,7 +52,38 @@
44
52
  "page",
45
53
  "size",
46
54
  "q",
47
- "sort"
55
+ "sort",
56
+ "tag"
57
+ ],
58
+ "body": false
59
+ }
60
+ },
61
+ {
62
+ "name": "list_tags",
63
+ "title": "List tags",
64
+ "description": "List every tag used in the organization, with how many monitors and how many heartbeats carry each one, most-used first. Call this before tagging something new so you reuse the names already in use rather than inventing a near-duplicate. Pass kind=monitor or kind=heartbeat to list only the tags that kind of item uses; both counts are reported either way.",
65
+ "scope": "read",
66
+ "input_schema": {
67
+ "type": "object",
68
+ "properties": {
69
+ "kind": {
70
+ "enum": [
71
+ "monitor",
72
+ "heartbeat",
73
+ "all"
74
+ ],
75
+ "type": "string"
76
+ }
77
+ },
78
+ "required": [],
79
+ "additionalProperties": false
80
+ },
81
+ "operation": {
82
+ "method": "GET",
83
+ "path": "/v1/tags",
84
+ "path_params": [],
85
+ "query": [
86
+ "kind"
48
87
  ],
49
88
  "body": false
50
89
  }
@@ -80,7 +119,7 @@
80
119
  {
81
120
  "name": "create_monitor",
82
121
  "title": "Create a monitor",
83
- "description": "Create a monitor that checks a URL, host or port on a schedule. config's shape depends on type: HTTP takes method, expected_status_codes, follow_redirects, verify_ssl, request_headers, request_body, all optional — omit config for HTTP to use its defaults; PING takes packet_count, optional; SSL takes warn_before_days, port, both optional; TCP requires port (no default) and takes optional expected_state (defaults to OPEN); DNS requires record_type (no default) and takes optional expected_value. Omitting config on a TCP or DNS monitor is rejected with a 422 for the missing field. regions accepts one or more of: eu-central, us-east, ap-southeast — except SSL and DNS monitors, which must target exactly one region (422 otherwise). How many regions a single monitor may use is also capped by plan, as low as 2 on FREE and PLUS. interval_seconds must be at least 30, but lower plans enforce a higher floor: FREE requires at least 120 seconds. Requires a key with the write scope.",
122
+ "description": "Create a monitor that checks a URL, host or port on a schedule. config's shape depends on type: HTTP takes method (GET, HEAD, POST, PUT, PATCH or DELETE), expected_status_codes, keyword (substring the response body must contain; not allowed with HEAD), follow_redirects, verify_ssl (false skips TLS verification, which also means that monitor can no longer report a TLS_INVALID failure), request_headers (a header name colliding case-insensitively with another is rejected) and request_body (request_body requires POST, PUT, PATCH or DELETE), all optional — omit config for HTTP to use its defaults. Reads never return request_header values: each one comes back as the literal __FETTLE_UNCHANGED__, and writing that value back keeps the stored header unchanged. PING takes packet_count, optional; SSL takes warn_before_days, port, both optional; TCP requires port (no default) and takes optional expected_state (defaults to OPEN); DNS requires record_type (no default) and takes optional expected_value. Omitting config on a TCP or DNS monitor is rejected with a 422 for the missing field. regions accepts one or more of: eu-central, us-east, ap-southeast — except SSL and DNS monitors, which must target exactly one region (422 otherwise). How many regions a single monitor may use is also capped by plan: 1 on FREE, 2 on PLUS. interval_seconds must be at least 30, but lower plans enforce a higher floor: FREE requires at least 180 seconds, PLUS and PRO at least 60. On MAX at most 50 monitors may use an interval below 60 seconds; past that the call fails with 403 'Fast interval limit exceeded' — use 60 or more, or slow another monitor down first. Requires a key with the write scope. tags is an optional list of flat labels (for example env:prod, service:api) used to group and filter monitors; they are lowercased and deduplicated on write, at most 10 per monitor, each at most 50 characters of lowercase letters, digits and : _ . / - . Call list_tags first to reuse the organization's existing names instead of inventing near-duplicates.",
84
123
  "scope": "write",
85
124
  "input_schema": {
86
125
  "type": "object",
@@ -142,6 +181,12 @@
142
181
  "type": "string"
143
182
  },
144
183
  "type": "array"
184
+ },
185
+ "tags": {
186
+ "items": {
187
+ "type": "string"
188
+ },
189
+ "type": "array"
145
190
  }
146
191
  },
147
192
  "required": [
@@ -165,7 +210,7 @@
165
210
  {
166
211
  "name": "update_monitor",
167
212
  "title": "Update a monitor",
168
- "description": "Change an existing monitor's settings. Only the fields you pass are modified; at least one field is required, or the request is rejected. config is the exception: passing it replaces the monitor's entire config, not just the keys you include — any field of that type's schema you leave out is reset to its default. Passing only port on a TCP monitor whose expected_state is CLOSED silently resets expected_state to OPEN unless you include it too. config's shape depends on the monitor's type: HTTP takes method, expected_status_codes, follow_redirects, verify_ssl, request_headers, request_body, all optional; PING takes packet_count, optional; SSL takes warn_before_days, port, both optional; TCP requires port (no default) and takes optional expected_state (defaults to OPEN); DNS requires record_type (no default) and takes optional expected_value. regions accepts one or more of: eu-central, us-east, ap-southeast — except SSL and DNS monitors, which must target exactly one region (422 otherwise). How many regions a single monitor may use is also capped by plan, as low as 2 on FREE and PLUS. interval_seconds must be at least 30, but lower plans enforce a higher floor: FREE requires at least 120 seconds. Requires a key with the write scope.",
213
+ "description": "Change an existing monitor's settings. Only the fields you pass are modified; at least one field is required, or the request is rejected. config is the exception: passing it replaces the monitor's entire config, not just the keys you include — any field of that type's schema you leave out is reset to its default. Passing only port on a TCP monitor whose expected_state is CLOSED silently resets expected_state to OPEN unless you include it too. config's shape depends on the monitor's type: HTTP takes method (GET, HEAD, POST, PUT, PATCH or DELETE), expected_status_codes, keyword (substring the response body must contain; not allowed with HEAD), follow_redirects, verify_ssl (false skips TLS verification, which also means that monitor can no longer report a TLS_INVALID failure), request_headers (a header name colliding case-insensitively with another is rejected) and request_body (request_body requires POST, PUT, PATCH or DELETE), all optional. Reads never return request_header values: each one comes back as the literal __FETTLE_UNCHANGED__, and writing that value back keeps the stored header unchanged; PING takes packet_count, optional; SSL takes warn_before_days, port, both optional; TCP requires port (no default) and takes optional expected_state (defaults to OPEN); DNS requires record_type (no default) and takes optional expected_value. regions accepts one or more of: eu-central, us-east, ap-southeast — except SSL and DNS monitors, which must target exactly one region (422 otherwise). How many regions a single monitor may use is also capped by plan: 1 on FREE, 2 on PLUS. interval_seconds must be at least 30, but lower plans enforce a higher floor: FREE requires at least 180 seconds, PLUS and PRO at least 60. On MAX at most 50 monitors may use an interval below 60 seconds; moving a monitor below 60 seconds past that fails with 403 'Fast interval limit exceeded' — use 60 or more, or slow another monitor down first. Requires a key with the write scope. tags, when passed, replaces the monitor's whole tag list; pass an empty list to clear it.",
169
214
  "scope": "write",
170
215
  "input_schema": {
171
216
  "type": "object",
@@ -219,6 +264,12 @@
219
264
  },
220
265
  "badge_enabled": {
221
266
  "type": "boolean"
267
+ },
268
+ "tags": {
269
+ "items": {
270
+ "type": "string"
271
+ },
272
+ "type": "array"
222
273
  }
223
274
  },
224
275
  "required": [
@@ -348,6 +399,52 @@
348
399
  "body": false
349
400
  }
350
401
  },
402
+ {
403
+ "name": "bulk_update_monitor_tags",
404
+ "title": "Retag several monitors at once",
405
+ "description": "Add and/or remove tags across up to 300 monitors in one call — the way to retag a whole fleet without one request per monitor. Every monitor_id must belong to the organization; an unknown id rejects the whole request, naming every id it could not find. add and remove apply as (current tags | add) - remove per monitor. A tag listed in both add and remove is rejected with a 422, not silently resolved — the two lists must be disjoint. At least one of add or remove is required. A monitor that would end up with more than 10 tags also rejects the whole request. Tags are lowercased and deduplicated on write, each at most 50 characters of lowercase letters, digits and : _ . / - . Call list_tags first to reuse the organization's existing names instead of inventing near-duplicates. Requires a key with the write scope.",
406
+ "scope": "write",
407
+ "input_schema": {
408
+ "type": "object",
409
+ "properties": {
410
+ "monitor_ids": {
411
+ "description": "Monitors to retag. Every id must belong to the active organization.",
412
+ "items": {
413
+ "format": "uuid",
414
+ "type": "string"
415
+ },
416
+ "maxItems": 300,
417
+ "minItems": 1,
418
+ "type": "array"
419
+ },
420
+ "add": {
421
+ "description": "Tags to add to every listed monitor.",
422
+ "items": {
423
+ "type": "string"
424
+ },
425
+ "type": "array"
426
+ },
427
+ "remove": {
428
+ "description": "Tags to remove from every listed monitor.",
429
+ "items": {
430
+ "type": "string"
431
+ },
432
+ "type": "array"
433
+ }
434
+ },
435
+ "required": [
436
+ "monitor_ids"
437
+ ],
438
+ "additionalProperties": false
439
+ },
440
+ "operation": {
441
+ "method": "POST",
442
+ "path": "/v1/monitors/tags",
443
+ "path_params": [],
444
+ "query": [],
445
+ "body": true
446
+ }
447
+ },
351
448
  {
352
449
  "name": "get_uptime_summary",
353
450
  "title": "Get uptime for a monitor",
@@ -430,7 +527,7 @@
430
527
  "maximum": 365,
431
528
  "minimum": 1,
432
529
  "type": "integer",
433
- "description": "Only incidents started within the last N days."
530
+ "description": "Only incidents that were active within the last N days. An incident still open is always included, whenever it started. Combined with status=open this makes period_days a no-op, since every open incident already satisfies that arm — use status=resolved or omit status to see the window take effect."
434
531
  }
435
532
  },
436
533
  "required": [],
@@ -482,7 +579,7 @@
482
579
  {
483
580
  "name": "list_heartbeats",
484
581
  "title": "List heartbeat monitors",
485
- "description": "List heartbeat (cron / dead-man's-switch) monitors and when each was last pinged.",
582
+ "description": "List heartbeat (cron / dead-man's-switch) monitors and when each was last pinged. Pass tag one or more times to narrow the list to heartbeats carrying every tag given (AND, not OR).",
486
583
  "scope": "read",
487
584
  "input_schema": {
488
585
  "type": "object",
@@ -507,6 +604,14 @@
507
604
  "created_at"
508
605
  ],
509
606
  "type": "string"
607
+ },
608
+ "tag": {
609
+ "items": {
610
+ "maxLength": 50,
611
+ "type": "string"
612
+ },
613
+ "maxItems": 10,
614
+ "type": "array"
510
615
  }
511
616
  },
512
617
  "required": [],
@@ -520,7 +625,8 @@
520
625
  "page",
521
626
  "size",
522
627
  "q",
523
- "sort"
628
+ "sort",
629
+ "tag"
524
630
  ],
525
631
  "body": false
526
632
  }
@@ -528,7 +634,7 @@
528
634
  {
529
635
  "name": "create_heartbeat",
530
636
  "title": "Create a heartbeat monitor",
531
- "description": "Create a heartbeat monitor and get back the URL a scheduled job should ping. Requires a key with the write scope.",
637
+ "description": "Create a heartbeat monitor and get back the URL a scheduled job should ping. Requires a key with the write scope. tags is an optional list of flat labels (for example env:prod, service:api) used to group and filter heartbeats; they are lowercased and deduplicated on write, at most 10 per heartbeat, each at most 50 characters of lowercase letters, digits and : _ . / - . Call list_tags first to reuse the organization's existing names instead of inventing near-duplicates.",
532
638
  "scope": "write",
533
639
  "input_schema": {
534
640
  "type": "object",
@@ -559,6 +665,12 @@
559
665
  "type": "string"
560
666
  },
561
667
  "type": "array"
668
+ },
669
+ "tags": {
670
+ "items": {
671
+ "type": "string"
672
+ },
673
+ "type": "array"
562
674
  }
563
675
  },
564
676
  "required": [
package/package.json CHANGED
@@ -1,15 +1,27 @@
1
1
  {
2
2
  "name": "fettle-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
+ "mcpName": "sh.fettle/mcp",
4
5
  "description": "MCP server for Fettle (fettle.sh) uptime monitoring — manage monitors, incidents, heartbeats and status pages from any MCP client.",
5
6
  "keywords": [
6
7
  "mcp",
8
+ "mcp-server",
7
9
  "modelcontextprotocol",
10
+ "model-context-protocol",
8
11
  "fettle",
12
+ "fettle.sh",
9
13
  "uptime",
10
- "monitoring",
14
+ "uptime-monitoring",
15
+ "website-monitoring",
16
+ "status-page",
17
+ "incidents",
18
+ "heartbeat",
19
+ "cron-monitoring",
20
+ "ssl-expiry",
11
21
  "claude",
12
- "cursor"
22
+ "claude-code",
23
+ "cursor",
24
+ "windsurf"
13
25
  ],
14
26
  "homepage": "https://fettle.sh",
15
27
  "repository": {