@crontinel/mcp-server 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/LICENSE +21 -0
- package/README.md +235 -0
- package/dist/index.js +119 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Harun R Rayhan
|
|
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,235 @@
|
|
|
1
|
+
# @crontinel/mcp-server
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@crontinel/mcp-server)
|
|
4
|
+
[](https://nodejs.org/)
|
|
5
|
+
[](https://github.com/crontinel/mcp-server/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/crontinel/mcp-server)
|
|
7
|
+
|
|
8
|
+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that connects AI assistants to [Crontinel](https://crontinel.com), the background job monitoring platform for Laravel. It runs as a local stdio process, proxying tool calls from your AI assistant to the Crontinel REST API.
|
|
9
|
+
|
|
10
|
+
Ask your AI assistant questions like "Did my cron jobs run last night?" or "What's the queue depth right now?" and get answers inline, without opening a browser.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Node.js 18+
|
|
15
|
+
- A Crontinel account with an API key ([app.crontinel.com](https://app.crontinel.com))
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx -y @crontinel/mcp-server
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or install globally:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @crontinel/mcp-server
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Configuration
|
|
30
|
+
|
|
31
|
+
### Claude Code
|
|
32
|
+
|
|
33
|
+
Add to `~/.claude/settings.json` (or use the Claude Code settings UI):
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"crontinel": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["-y", "@crontinel/mcp-server"],
|
|
41
|
+
"env": {
|
|
42
|
+
"CRONTINEL_API_KEY": "your-api-key-here"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Cursor
|
|
50
|
+
|
|
51
|
+
Add to `~/.cursor/mcp.json` or the project-level `.cursor/mcp.json`:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"crontinel": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "@crontinel/mcp-server"],
|
|
59
|
+
"env": {
|
|
60
|
+
"CRONTINEL_API_KEY": "your-api-key-here"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Environment Variables
|
|
68
|
+
|
|
69
|
+
| Variable | Required | Default | Description |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| `CRONTINEL_API_KEY` | Yes | n/a | Your Crontinel API key |
|
|
72
|
+
| `CRONTINEL_API_URL` | No | `https://app.crontinel.com` | Override the API base URL (self-hosted or local dev) |
|
|
73
|
+
|
|
74
|
+
## Available Tools
|
|
75
|
+
|
|
76
|
+
| Tool | Description |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `list_scheduled_jobs` | List all monitored cron commands with last run status |
|
|
79
|
+
| `get_cron_status` | Last run details for a specific command (exit code, duration, output) |
|
|
80
|
+
| `get_queue_status` | Depth, failed count, and wait time for queues |
|
|
81
|
+
| `get_horizon_status` | Horizon supervisor health snapshot (status, failed/min) |
|
|
82
|
+
| `list_recent_alerts` | Alerts fired in the last N hours |
|
|
83
|
+
| `acknowledge_alert` | Dismiss an active alert by its key |
|
|
84
|
+
| `create_alert` | Create a new alert channel (Slack, email, or webhook) |
|
|
85
|
+
|
|
86
|
+
### `list_scheduled_jobs`
|
|
87
|
+
|
|
88
|
+
List all monitored cron jobs for an app, with their last run status and timing.
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
|
|
92
|
+
| Name | Type | Required | Description |
|
|
93
|
+
|---|---|---|---|
|
|
94
|
+
| `app_slug` | string | Yes | App slug from your Crontinel dashboard |
|
|
95
|
+
|
|
96
|
+
**Returns:** Array of job objects with `command`, `schedule`, `last_run_at`, `last_exit_code`, `last_duration_ms`, `status` (`ok` / `late` / `failing` / `never_ran`).
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
### `get_cron_status`
|
|
101
|
+
|
|
102
|
+
Get the last run result for a specific cron command.
|
|
103
|
+
|
|
104
|
+
**Parameters:**
|
|
105
|
+
|
|
106
|
+
| Name | Type | Required | Description |
|
|
107
|
+
|---|---|---|---|
|
|
108
|
+
| `app_slug` | string | Yes | App slug |
|
|
109
|
+
| `command` | string | Yes | The cron command string (e.g. `php artisan inspire`) |
|
|
110
|
+
|
|
111
|
+
**Returns:** `command`, `last_run_at`, `exit_code`, `duration_ms`, `output` (last 500 chars of stdout/stderr), `status`.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### `get_queue_status`
|
|
116
|
+
|
|
117
|
+
Get queue depth, failed count, and oldest pending job age.
|
|
118
|
+
|
|
119
|
+
**Parameters:**
|
|
120
|
+
|
|
121
|
+
| Name | Type | Required | Description |
|
|
122
|
+
|---|---|---|---|
|
|
123
|
+
| `app_slug` | string | Yes | App slug |
|
|
124
|
+
| `queue` | string | No | Specific queue name; omit for all queues |
|
|
125
|
+
|
|
126
|
+
**Returns:** Array of queue objects with `name`, `depth`, `failed_count`, `oldest_job_age_seconds`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### `get_horizon_status`
|
|
131
|
+
|
|
132
|
+
Get a health snapshot of Laravel Horizon: supervisor states, paused/running, failed jobs per minute.
|
|
133
|
+
|
|
134
|
+
**Parameters:**
|
|
135
|
+
|
|
136
|
+
| Name | Type | Required | Description |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| `app_slug` | string | Yes | App slug |
|
|
139
|
+
|
|
140
|
+
**Returns:** `status` (`running` / `paused` / `inactive`), `failed_jobs_per_minute`, `supervisors` array with `name`, `status`, `processes`.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
### `list_recent_alerts`
|
|
145
|
+
|
|
146
|
+
List alerts that have fired within the last N hours.
|
|
147
|
+
|
|
148
|
+
**Parameters:**
|
|
149
|
+
|
|
150
|
+
| Name | Type | Required | Description |
|
|
151
|
+
|---|---|---|---|
|
|
152
|
+
| `app_slug` | string | Yes | App slug |
|
|
153
|
+
| `hours` | number | No | Look-back window in hours (default: 24) |
|
|
154
|
+
|
|
155
|
+
**Returns:** Array of alert objects with `alert_key`, `state` (`firing` / `resolved`), `fired_at`, `resolved_at`, `message`.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
### `acknowledge_alert`
|
|
160
|
+
|
|
161
|
+
Dismiss an active alert so it stops notifying.
|
|
162
|
+
|
|
163
|
+
**Parameters:**
|
|
164
|
+
|
|
165
|
+
| Name | Type | Required | Description |
|
|
166
|
+
|---|---|---|---|
|
|
167
|
+
| `app_slug` | string | Yes | App slug |
|
|
168
|
+
| `alert_key` | string | Yes | Alert key (from `list_recent_alerts`) |
|
|
169
|
+
|
|
170
|
+
**Returns:** `{ acknowledged: true, alert_key: "..." }` on success.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### `create_alert`
|
|
175
|
+
|
|
176
|
+
Create a new alert channel for an app. Requires a Pro or Team plan.
|
|
177
|
+
|
|
178
|
+
**Parameters:**
|
|
179
|
+
|
|
180
|
+
| Name | Type | Required | Description |
|
|
181
|
+
|---|---|---|---|
|
|
182
|
+
| `app_slug` | string | Yes | App slug |
|
|
183
|
+
| `type` | string | Yes | `slack`, `email`, or `webhook` |
|
|
184
|
+
| `config` | object | Yes | Channel-specific config (see below) |
|
|
185
|
+
|
|
186
|
+
**Config by type:**
|
|
187
|
+
|
|
188
|
+
| Type | Required fields |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `slack` | `webhook_url`: Incoming Webhook URL |
|
|
191
|
+
| `email` | `address`: Recipient email address |
|
|
192
|
+
| `webhook` | `url`: Endpoint URL; optionally `secret` for HMAC signing |
|
|
193
|
+
|
|
194
|
+
**Returns:** The new alert channel ID on success.
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## How It Works
|
|
199
|
+
|
|
200
|
+
1. Your AI assistant spawns the MCP server as a local stdio process
|
|
201
|
+
2. The server receives JSON-RPC tool calls over stdin
|
|
202
|
+
3. It forwards each call as an HTTP request to `app.crontinel.com/api/mcp` with your API key in the `Authorization` header
|
|
203
|
+
4. The JSON-RPC response is returned over stdout
|
|
204
|
+
|
|
205
|
+
All tool definitions are declared locally so your AI can inspect them without a network round-trip.
|
|
206
|
+
|
|
207
|
+
## Troubleshooting
|
|
208
|
+
|
|
209
|
+
**`401 Unauthorized`**: Your `CRONTINEL_API_KEY` is missing or invalid. Check that the env var is set in your MCP config, not your shell profile (MCP servers don't inherit your shell environment).
|
|
210
|
+
|
|
211
|
+
**`404 Not Found` on a tool call**: The `app_slug` doesn't match any app in your account. Copy the slug from the app URL in the Crontinel dashboard (`app.crontinel.com/apps/{slug}`).
|
|
212
|
+
|
|
213
|
+
**Tools not showing up in Claude/Cursor**: Restart the AI client after updating the MCP config. Most clients only load MCP servers at startup.
|
|
214
|
+
|
|
215
|
+
**`npx` slow on first run**: `npx -y` downloads the package on first use. Run `npm install -g @crontinel/mcp-server` once to cache it locally, then change `command` to `crontinel-mcp` and remove the `args`.
|
|
216
|
+
|
|
217
|
+
## Documentation
|
|
218
|
+
|
|
219
|
+
For the full integration guide, tool reference, and setup walkthroughs:
|
|
220
|
+
|
|
221
|
+
- [MCP Overview](https://docs.crontinel.com/mcp/overview/)
|
|
222
|
+
- [Available Tools Reference](https://docs.crontinel.com/mcp/tools/)
|
|
223
|
+
- [Claude Code Setup](https://docs.crontinel.com/mcp/claude-code/)
|
|
224
|
+
|
|
225
|
+
## Ecosystem
|
|
226
|
+
|
|
227
|
+
| Package | Description |
|
|
228
|
+
|---|---|
|
|
229
|
+
| [@crontinel/mcp-server](https://github.com/crontinel/mcp-server) | MCP server for AI assistants (this repo) |
|
|
230
|
+
| [crontinel/laravel](https://github.com/crontinel/crontinel) | Laravel package that reports the data this server reads |
|
|
231
|
+
| [docs.crontinel.com](https://docs.crontinel.com) | Full documentation |
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
[MIT](https://github.com/crontinel/mcp-server/blob/main/LICENSE)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
7
|
+
const API_KEY = process.env.CRONTINEL_API_KEY;
|
|
8
|
+
const API_URL = process.env.CRONTINEL_API_URL ?? 'https://app.crontinel.com';
|
|
9
|
+
if (!API_KEY) {
|
|
10
|
+
console.error('Error: CRONTINEL_API_KEY environment variable is required');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const TOOLS = [
|
|
14
|
+
{
|
|
15
|
+
name: 'list_scheduled_jobs',
|
|
16
|
+
description: 'List all monitored cron commands with their last run status and timing',
|
|
17
|
+
inputSchema: { type: 'object', properties: {} },
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'get_cron_status',
|
|
21
|
+
description: 'Get the last run status, exit code, and duration for a specific cron command',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
command: { type: 'string', description: 'Command name or partial match (e.g. "send-invoices")' },
|
|
26
|
+
},
|
|
27
|
+
required: ['command'],
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'get_queue_status',
|
|
32
|
+
description: 'Get queue depth, failed job count, and oldest job age for all queues or a specific queue',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
queue: { type: 'string', description: 'Queue name (optional — returns all queues if omitted)' },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'get_horizon_status',
|
|
42
|
+
description: 'Get a snapshot of Laravel Horizon health: supervisor states, paused status, failed jobs per minute',
|
|
43
|
+
inputSchema: { type: 'object', properties: {} },
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
name: 'list_recent_alerts',
|
|
47
|
+
description: 'List alerts that have fired in the last N hours',
|
|
48
|
+
inputSchema: {
|
|
49
|
+
type: 'object',
|
|
50
|
+
properties: {
|
|
51
|
+
hours: { type: 'integer', description: 'Look-back window in hours (default: 24)' },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'acknowledge_alert',
|
|
57
|
+
description: 'Dismiss an active alert by its alert key',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: 'object',
|
|
60
|
+
properties: {
|
|
61
|
+
alert_key: { type: 'string', description: 'Alert key to dismiss (e.g. "horizon:paused", "queue:emails:depth")' },
|
|
62
|
+
},
|
|
63
|
+
required: ['alert_key'],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'create_alert',
|
|
68
|
+
description: 'Create a new alert channel (slack, email, or webhook) for the app',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
type: { type: 'string', enum: ['slack', 'email', 'webhook'], description: 'Alert channel type' },
|
|
73
|
+
webhook_url: { type: 'string', description: 'Slack incoming webhook URL (required when type=slack)' },
|
|
74
|
+
to: { type: 'string', description: 'Recipient email address (required when type=email)' },
|
|
75
|
+
url: { type: 'string', description: 'Webhook endpoint URL (required when type=webhook)' },
|
|
76
|
+
},
|
|
77
|
+
required: ['type'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
];
|
|
81
|
+
async function callCrontinel(method, params) {
|
|
82
|
+
const response = await fetch(`${API_URL}/api/mcp`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
'Authorization': `Bearer ${API_KEY}`,
|
|
87
|
+
},
|
|
88
|
+
body: JSON.stringify({
|
|
89
|
+
jsonrpc: '2.0',
|
|
90
|
+
id: Date.now(),
|
|
91
|
+
method,
|
|
92
|
+
params,
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) {
|
|
96
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Crontinel API error: ${response.status} ${response.statusText}`);
|
|
97
|
+
}
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
if (data.error) {
|
|
100
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, data.error.message);
|
|
101
|
+
}
|
|
102
|
+
return data.result;
|
|
103
|
+
}
|
|
104
|
+
const server = new index_js_1.Server({ name: 'crontinel', version: '0.2.0' }, { capabilities: { tools: {} } });
|
|
105
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
106
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
107
|
+
const { name, arguments: args } = request.params;
|
|
108
|
+
const result = await callCrontinel('tools/call', { name, arguments: args ?? {} });
|
|
109
|
+
return result;
|
|
110
|
+
});
|
|
111
|
+
async function main() {
|
|
112
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
console.error('Crontinel MCP server running');
|
|
115
|
+
}
|
|
116
|
+
main().catch((err) => {
|
|
117
|
+
console.error('Fatal error:', err);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crontinel/mcp-server",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "MCP server for Crontinel background job monitoring",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist/",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"bin": {
|
|
11
|
+
"crontinel-mcp": "dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "rm -rf dist && tsc && chmod +x dist/index.js",
|
|
15
|
+
"dev": "ts-node src/index.ts",
|
|
16
|
+
"prepublishOnly": "npm run build",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"lint": "eslint src --ext .ts"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"laravel",
|
|
25
|
+
"monitoring",
|
|
26
|
+
"cron",
|
|
27
|
+
"horizon",
|
|
28
|
+
"queue"
|
|
29
|
+
],
|
|
30
|
+
"author": "Harun R Rayhan",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"homepage": "https://crontinel.com",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/crontinel/mcp-server.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/crontinel/mcp-server/issues"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"typescript": "^5.0.0",
|
|
45
|
+
"@types/node": "^20.0.0",
|
|
46
|
+
"vitest": "^1.0.0",
|
|
47
|
+
"@vitest/coverage-v8": "^1.0.0"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=18"
|
|
51
|
+
}
|
|
52
|
+
}
|