@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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +235 -0
  3. package/dist/index.js +119 -0
  4. 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
+ [![npm version](https://img.shields.io/npm/v/@crontinel/mcp-server)](https://www.npmjs.com/package/@crontinel/mcp-server)
4
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org/)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/crontinel/mcp-server/blob/main/LICENSE)
6
+ [![GitHub stars](https://img.shields.io/github/stars/crontinel/mcp-server)](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
+ }