n8n-guard 0.1.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.
@@ -0,0 +1,44 @@
1
+ import type { GuardConfig } from "../config.js";
2
+ export interface N8nWorkflow {
3
+ id: string;
4
+ name: string;
5
+ active: boolean;
6
+ updatedAt?: string;
7
+ createdAt?: string;
8
+ nodes?: unknown[];
9
+ connections?: Record<string, unknown>;
10
+ settings?: Record<string, unknown>;
11
+ }
12
+ export interface N8nExecution {
13
+ id: string | number;
14
+ workflowId?: string;
15
+ status?: string;
16
+ finished?: boolean;
17
+ mode?: string;
18
+ startedAt?: string | null;
19
+ stoppedAt?: string | null;
20
+ }
21
+ export declare class N8nApiError extends Error {
22
+ readonly status?: number | undefined;
23
+ constructor(message: string, status?: number | undefined);
24
+ }
25
+ /**
26
+ * Thin client for the n8n Public API. It deliberately covers only what the
27
+ * operational checks need; workflow authoring is what every other n8n MCP
28
+ * server already does well.
29
+ */
30
+ export declare class N8nApi {
31
+ private readonly config;
32
+ constructor(config: GuardConfig);
33
+ private request;
34
+ /** Walks the cursor pagination until exhausted or `max` records are collected. */
35
+ private collect;
36
+ /** Reachability probe that works without an API key. */
37
+ health(): Promise<{
38
+ reachable: boolean;
39
+ detail: string;
40
+ }>;
41
+ listWorkflows(max?: number): Promise<N8nWorkflow[]>;
42
+ getWorkflow(id: string): Promise<N8nWorkflow>;
43
+ listExecutions(status: "running" | "success" | "error" | "waiting" | undefined, max?: number): Promise<N8nExecution[]>;
44
+ }
@@ -0,0 +1,77 @@
1
+ export class N8nApiError extends Error {
2
+ status;
3
+ constructor(message, status) {
4
+ super(message);
5
+ this.status = status;
6
+ this.name = "N8nApiError";
7
+ }
8
+ }
9
+ /**
10
+ * Thin client for the n8n Public API. It deliberately covers only what the
11
+ * operational checks need; workflow authoring is what every other n8n MCP
12
+ * server already does well.
13
+ */
14
+ export class N8nApi {
15
+ config;
16
+ constructor(config) {
17
+ this.config = config;
18
+ }
19
+ async request(path, params = {}) {
20
+ if (!this.config.n8nApiKey) {
21
+ throw new N8nApiError("N8N_API_KEY is not set, so the n8n Public API cannot be queried");
22
+ }
23
+ const url = new URL(`${this.config.n8nUrl}/api/v1${path}`);
24
+ for (const [key, value] of Object.entries(params)) {
25
+ if (value !== undefined)
26
+ url.searchParams.set(key, String(value));
27
+ }
28
+ let response;
29
+ try {
30
+ response = await fetch(url, {
31
+ headers: { "X-N8N-API-KEY": this.config.n8nApiKey, accept: "application/json" },
32
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs),
33
+ });
34
+ }
35
+ catch (error) {
36
+ throw new N8nApiError(`cannot reach ${url.origin}: ${error.message}`);
37
+ }
38
+ if (!response.ok) {
39
+ const body = await response.text().catch(() => "");
40
+ throw new N8nApiError(`${url.pathname} returned ${response.status} ${response.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`, response.status);
41
+ }
42
+ return (await response.json());
43
+ }
44
+ /** Walks the cursor pagination until exhausted or `max` records are collected. */
45
+ async collect(path, params, max) {
46
+ const out = [];
47
+ let cursor;
48
+ do {
49
+ const page = await this.request(path, { ...params, limit: 250, cursor });
50
+ out.push(...(page.data ?? []));
51
+ cursor = page.nextCursor ?? undefined;
52
+ } while (cursor && out.length < max);
53
+ return out.slice(0, max);
54
+ }
55
+ /** Reachability probe that works without an API key. */
56
+ async health() {
57
+ try {
58
+ const response = await fetch(`${this.config.n8nUrl}/healthz`, {
59
+ signal: AbortSignal.timeout(this.config.requestTimeoutMs),
60
+ });
61
+ return { reachable: response.ok, detail: `GET /healthz -> ${response.status}` };
62
+ }
63
+ catch (error) {
64
+ return { reachable: false, detail: error.message };
65
+ }
66
+ }
67
+ listWorkflows(max = 1000) {
68
+ return this.collect("/workflows", { excludePinnedData: true }, max);
69
+ }
70
+ getWorkflow(id) {
71
+ return this.request(`/workflows/${encodeURIComponent(id)}`);
72
+ }
73
+ listExecutions(status, max = 500) {
74
+ return this.collect("/executions", { status, includeData: false }, max);
75
+ }
76
+ }
77
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/n8n/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { GuardConfig } from "../config.js";
2
+ export type DatastoreKind = "sqlite" | "postgres";
3
+ export interface TableStat {
4
+ table: string;
5
+ rows: number;
6
+ /** Bytes on disk. `null` when the engine cannot report it (SQLite without the dbstat module). */
7
+ bytes: number | null;
8
+ }
9
+ export interface DatastoreStats {
10
+ kind: DatastoreKind;
11
+ /** Total size of the n8n datastore in bytes. */
12
+ totalBytes: number;
13
+ /** Extra files counted into `totalBytes` (SQLite WAL/SHM), for transparency. */
14
+ components: {
15
+ name: string;
16
+ bytes: number;
17
+ }[];
18
+ tables: TableStat[];
19
+ executionCount: number;
20
+ oldestExecutionAt: string | null;
21
+ newestExecutionAt: string | null;
22
+ /** Execution rows grouped by age bucket, for retention decisions. */
23
+ executionsOlderThanDays: Record<string, number>;
24
+ }
25
+ export declare class DatastoreError extends Error {
26
+ }
27
+ export declare const RETENTION_BUCKETS: number[];
28
+ export declare function resolveDatastoreKind(config: GuardConfig): DatastoreKind;
29
+ export declare function readDatastoreStats(config: GuardConfig): Promise<DatastoreStats>;
@@ -0,0 +1,169 @@
1
+ import { stat } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { openSqlite } from "./sqlite.js";
4
+ export class DatastoreError extends Error {
5
+ }
6
+ /** Tables worth measuring. Missing ones are skipped, n8n schemas differ across versions. */
7
+ const INTERESTING_TABLES = [
8
+ "execution_entity",
9
+ "execution_data",
10
+ "execution_metadata",
11
+ "execution_annotation",
12
+ "workflow_entity",
13
+ "workflow_history",
14
+ "credentials_entity",
15
+ "webhook_entity",
16
+ "insights_raw",
17
+ ];
18
+ export const RETENTION_BUCKETS = [7, 30, 90, 365];
19
+ export function resolveDatastoreKind(config) {
20
+ if (config.postgresUrl)
21
+ return "postgres";
22
+ if (config.sqlitePath)
23
+ return "sqlite";
24
+ throw new DatastoreError("no datastore configured: set N8N_SQLITE_PATH (SQLite) or N8N_POSTGRES_URL (Postgres)");
25
+ }
26
+ export async function readDatastoreStats(config) {
27
+ return resolveDatastoreKind(config) === "postgres"
28
+ ? readPostgresStats(config.postgresUrl)
29
+ : readSqliteStats(config.sqlitePath);
30
+ }
31
+ function bucketCutoffs(now = Date.now()) {
32
+ return RETENTION_BUCKETS.map((days) => {
33
+ const epochMs = now - days * 86_400_000;
34
+ return { days, iso: new Date(epochMs).toISOString(), epochMs };
35
+ });
36
+ }
37
+ async function readSqliteStats(sqlitePath) {
38
+ let main;
39
+ try {
40
+ main = (await stat(sqlitePath)).size;
41
+ }
42
+ catch (error) {
43
+ throw new DatastoreError(`cannot read ${sqlitePath}: ${error.message}`);
44
+ }
45
+ const components = [{ name: path.basename(sqlitePath), bytes: main }];
46
+ for (const suffix of ["-wal", "-shm"]) {
47
+ const size = await stat(sqlitePath + suffix).then((s) => s.size).catch(() => null);
48
+ if (size !== null)
49
+ components.push({ name: path.basename(sqlitePath) + suffix, bytes: size });
50
+ }
51
+ const db = openSqlite(sqlitePath, { readOnly: true });
52
+ try {
53
+ const present = new Set(db.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((r) => String(r.name)));
54
+ // dbstat is an optional SQLite module; without it we still report row counts.
55
+ let perTableBytes = null;
56
+ try {
57
+ const rows = db.prepare("SELECT name, SUM(pgsize) AS bytes FROM dbstat GROUP BY name").all();
58
+ perTableBytes = new Map(rows.map((r) => [String(r.name), Number(r.bytes)]));
59
+ }
60
+ catch {
61
+ perTableBytes = null;
62
+ }
63
+ const tables = [];
64
+ for (const table of INTERESTING_TABLES) {
65
+ if (!present.has(table))
66
+ continue;
67
+ const row = db.prepare(`SELECT COUNT(*) AS n FROM "${table}"`).get();
68
+ tables.push({ table, rows: Number(row?.n ?? 0), bytes: perTableBytes?.get(table) ?? null });
69
+ }
70
+ let executionCount = 0;
71
+ let oldestExecutionAt = null;
72
+ let newestExecutionAt = null;
73
+ const executionsOlderThanDays = {};
74
+ if (present.has("execution_entity")) {
75
+ const agg = db
76
+ .prepare(`SELECT COUNT(*) AS n, MIN("startedAt") AS oldest, MAX("startedAt") AS newest FROM execution_entity`)
77
+ .get();
78
+ executionCount = Number(agg?.n ?? 0);
79
+ oldestExecutionAt = normalizeTimestamp(agg?.oldest ?? null);
80
+ newestExecutionAt = normalizeTimestamp(agg?.newest ?? null);
81
+ // n8n has stored startedAt as ISO text and as epoch millis across versions.
82
+ // SQLite sorts every integer before every string, so a text-only comparison
83
+ // would count all numeric rows as older than any cutoff.
84
+ const counter = db.prepare(`
85
+ SELECT COUNT(*) AS n FROM execution_entity
86
+ WHERE CASE typeof("startedAt")
87
+ WHEN 'integer' THEN "startedAt" < ?
88
+ WHEN 'real' THEN "startedAt" < ?
89
+ ELSE "startedAt" < ?
90
+ END`);
91
+ for (const { days, iso, epochMs } of bucketCutoffs()) {
92
+ const hit = counter.get(epochMs, epochMs, iso);
93
+ executionsOlderThanDays[String(days)] = Number(hit?.n ?? 0);
94
+ }
95
+ }
96
+ return {
97
+ kind: "sqlite",
98
+ totalBytes: components.reduce((sum, c) => sum + c.bytes, 0),
99
+ components,
100
+ tables,
101
+ executionCount,
102
+ oldestExecutionAt,
103
+ newestExecutionAt,
104
+ executionsOlderThanDays,
105
+ };
106
+ }
107
+ finally {
108
+ db.close();
109
+ }
110
+ }
111
+ async function readPostgresStats(connectionString) {
112
+ const { default: pg } = await import("pg");
113
+ const client = new pg.Client({ connectionString, connectionTimeoutMillis: 10_000 });
114
+ try {
115
+ await client.connect();
116
+ }
117
+ catch (error) {
118
+ throw new DatastoreError(`cannot connect to Postgres: ${error.message}`);
119
+ }
120
+ try {
121
+ const total = await client.query("SELECT pg_database_size(current_database())::text AS bytes");
122
+ const present = await client.query("SELECT tablename FROM pg_tables WHERE schemaname = current_schema()");
123
+ const names = new Set(present.rows.map((r) => r.tablename));
124
+ const tables = [];
125
+ for (const table of INTERESTING_TABLES) {
126
+ if (!names.has(table))
127
+ continue;
128
+ const result = await client.query(`SELECT (SELECT COUNT(*) FROM "${table}")::text AS rows, pg_total_relation_size($1)::text AS bytes`, [table]);
129
+ const row = result.rows[0];
130
+ tables.push({ table, rows: Number(row?.rows ?? 0), bytes: Number(row?.bytes ?? 0) });
131
+ }
132
+ let executionCount = 0;
133
+ let oldestExecutionAt = null;
134
+ let newestExecutionAt = null;
135
+ const executionsOlderThanDays = {};
136
+ if (names.has("execution_entity")) {
137
+ const agg = await client.query(`SELECT COUNT(*)::text AS n, MIN("startedAt") AS oldest, MAX("startedAt") AS newest FROM execution_entity`);
138
+ const row = agg.rows[0];
139
+ executionCount = Number(row?.n ?? 0);
140
+ oldestExecutionAt = row?.oldest ? new Date(row.oldest).toISOString() : null;
141
+ newestExecutionAt = row?.newest ? new Date(row.newest).toISOString() : null;
142
+ for (const { days, iso } of bucketCutoffs()) {
143
+ const hit = await client.query(`SELECT COUNT(*)::text AS n FROM execution_entity WHERE "startedAt" < $1`, [iso]);
144
+ executionsOlderThanDays[String(days)] = Number(hit.rows[0]?.n ?? 0);
145
+ }
146
+ }
147
+ return {
148
+ kind: "postgres",
149
+ totalBytes: Number(total.rows[0]?.bytes ?? 0),
150
+ components: [{ name: "database", bytes: Number(total.rows[0]?.bytes ?? 0) }],
151
+ tables,
152
+ executionCount,
153
+ oldestExecutionAt,
154
+ newestExecutionAt,
155
+ executionsOlderThanDays,
156
+ };
157
+ }
158
+ finally {
159
+ await client.end().catch(() => undefined);
160
+ }
161
+ }
162
+ /** SQLite stores timestamps as text or epoch millis depending on the n8n version. */
163
+ function normalizeTimestamp(value) {
164
+ if (value === null || value === undefined)
165
+ return null;
166
+ const date = typeof value === "number" ? new Date(value) : new Date(value);
167
+ return Number.isNaN(date.getTime()) ? String(value) : date.toISOString();
168
+ }
169
+ //# sourceMappingURL=datastore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datastore.js","sourceRoot":"","sources":["../../src/n8n/datastore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ export interface SqliteStatement {
2
+ all(...params: unknown[]): Record<string, unknown>[];
3
+ get(...params: unknown[]): Record<string, unknown> | undefined;
4
+ run(...params: unknown[]): unknown;
5
+ }
6
+ export interface SqliteDatabase {
7
+ prepare(sql: string): SqliteStatement;
8
+ exec(sql: string): void;
9
+ close(): void;
10
+ }
11
+ export declare function openSqlite(path: string, options?: {
12
+ readOnly?: boolean;
13
+ }): SqliteDatabase;
@@ -0,0 +1,17 @@
1
+ import { createRequire } from "node:module";
2
+ /**
3
+ * `node:sqlite` is a Node builtin, but bundlers still try to resolve it from
4
+ * node_modules and fail. Going through createRequire hands it to Node directly.
5
+ */
6
+ const nodeRequire = createRequire(import.meta.url);
7
+ export function openSqlite(path, options = {}) {
8
+ let DatabaseSync;
9
+ try {
10
+ ({ DatabaseSync } = nodeRequire("node:sqlite"));
11
+ }
12
+ catch {
13
+ throw new Error("node:sqlite is unavailable; n8n-guard needs Node.js 22 or newer for SQLite support");
14
+ }
15
+ return new DatabaseSync(path, options);
16
+ }
17
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/n8n/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;GAGG;AACH,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAcnD,MAAM,UAAU,UAAU,CAAC,IAAY,EAAE,UAAkC,EAAE;IAC3E,IAAI,YAAoF,CAAC;IACzF,IAAI,CAAC;QACH,CAAC,EAAE,YAAY,EAAE,GAAG,WAAW,CAAC,aAAa,CAAC,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,oFAAoF,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,IAAI,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { GuardConfig } from "./config.js";
3
+ export declare const SERVER_NAME = "n8n-guard";
4
+ export declare const SERVER_VERSION = "0.1.0";
5
+ export declare function createServer(config: GuardConfig): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,115 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { GuardService } from "./guard/service.js";
4
+ import { errorMessage } from "./guard/runner.js";
5
+ export const SERVER_NAME = "n8n-guard";
6
+ export const SERVER_VERSION = "0.1.0";
7
+ function render(title, findings, payload) {
8
+ const text = [`## ${title}`, ...findings.map((f) => `- ${f}`), "", "```json", JSON.stringify(payload, null, 2), "```"].join("\n");
9
+ return { content: [{ type: "text", text }], structuredContent: payload };
10
+ }
11
+ /** Tool errors are answers too: the caller needs the reason, not a transport fault. */
12
+ function failure(title, error) {
13
+ return {
14
+ content: [{ type: "text", text: `## ${title}\n\n**failed:** ${errorMessage(error)}` }],
15
+ isError: true,
16
+ };
17
+ }
18
+ export function createServer(config) {
19
+ const guard = new GuardService(config);
20
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
21
+ server.registerTool("instance_info", {
22
+ title: "Instance info",
23
+ description: "Reachability, configuration and workflow inventory of the monitored n8n instance. Start here: it shows which checks are configured and which are not.",
24
+ inputSchema: {},
25
+ }, async () => {
26
+ try {
27
+ const info = await guard.instanceInfo();
28
+ const findings = [
29
+ info.reachable ? `${info.url} is reachable (${info.detail})` : `${info.url} is NOT reachable: ${info.detail}`,
30
+ `datastore: ${info.datastore}`,
31
+ info.apiKeyConfigured ? "API key configured" : "no API key: execution and workflow checks are unavailable",
32
+ info.gitRepoConfigured ? "git repository configured" : "no git repository: drift check is unavailable",
33
+ ];
34
+ if (info.workflowCount !== null)
35
+ findings.push(`${info.workflowCount} workflows, ${info.activeWorkflowCount} active`);
36
+ return render("Instance info", findings, info);
37
+ }
38
+ catch (error) {
39
+ return failure("Instance info", error);
40
+ }
41
+ });
42
+ server.registerTool("db_health", {
43
+ title: "Database health",
44
+ description: "Size, per-table breakdown and growth rate of the n8n datastore, plus an estimate of when it hits the critical threshold. Works on SQLite and Postgres. The n8n Public API has no endpoint for this.",
45
+ inputSchema: {},
46
+ }, async () => {
47
+ try {
48
+ const report = await guard.dbHealth();
49
+ return render(`Database health (${report.severity})`, report.findings, report);
50
+ }
51
+ catch (error) {
52
+ return failure("Database health", error);
53
+ }
54
+ });
55
+ server.registerTool("stuck_executions", {
56
+ title: "Stuck executions",
57
+ description: "Executions sitting in `running` past a threshold or far past their own workflow's median runtime. Listing by status is not detection: this compares against a baseline.",
58
+ inputSchema: {
59
+ threshold_minutes: z
60
+ .number()
61
+ .positive()
62
+ .optional()
63
+ .describe("Minutes a run may stay in `running` before it counts as stuck. Defaults to N8N_GUARD_STUCK_MINUTES."),
64
+ },
65
+ }, async ({ threshold_minutes }) => {
66
+ try {
67
+ const report = await guard.stuckExecutions(threshold_minutes);
68
+ return render("Stuck executions", report.findings, report);
69
+ }
70
+ catch (error) {
71
+ return failure("Stuck executions", error);
72
+ }
73
+ });
74
+ server.registerTool("retention_report", {
75
+ title: "Retention report",
76
+ description: "How many execution records are stored, how far back they go, and what pruning to a retention window would free. Read-only: n8n-guard never deletes anything.",
77
+ inputSchema: {
78
+ keep_days: z.number().positive().optional().describe("Retention window to evaluate, in days. Defaults to 30."),
79
+ },
80
+ }, async ({ keep_days }) => {
81
+ try {
82
+ const report = await guard.retention(keep_days ?? 30);
83
+ return render("Retention report", [...report.findings, report.hint], report);
84
+ }
85
+ catch (error) {
86
+ return failure("Retention report", error);
87
+ }
88
+ });
89
+ server.registerTool("git_drift", {
90
+ title: "Git drift",
91
+ description: "Diffs the workflows in the running instance against the JSON exports in a git checkout: live but unexported, exported but deleted, and content that has drifted apart.",
92
+ inputSchema: {},
93
+ }, async () => {
94
+ try {
95
+ const report = await guard.gitDrift();
96
+ return render("Git drift", report.findings, report);
97
+ }
98
+ catch (error) {
99
+ return failure("Git drift", error);
100
+ }
101
+ });
102
+ server.registerTool("guard_run", {
103
+ title: "Full guard run",
104
+ description: "Runs every check in one resilient sweep. Individual steps retry and, if they still fail, are reported as findings while the remaining checks finish. Use this for scheduled monitoring.",
105
+ inputSchema: {
106
+ keep_days: z.number().positive().optional().describe("Retention window used by the retention step. Defaults to 30."),
107
+ },
108
+ }, async ({ keep_days }) => {
109
+ const report = await guard.fullRun({ keepDays: keep_days ?? 30 });
110
+ const title = report.degraded ? "Guard run (degraded)" : "Guard run";
111
+ return render(title, report.findings, report);
112
+ });
113
+ return server;
114
+ }
115
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "n8n-guard",
3
+ "version": "0.1.0",
4
+ "mcpName": "io.github.flow-84/n8n-guard",
5
+ "description": "MCP server for the operational layer beneath the n8n Public API: disk, retention, stuck executions, git drift.",
6
+ "license": "MIT",
7
+ "author": "flow-84",
8
+ "homepage": "https://github.com/flow-84/n8n-guard#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/flow-84/n8n-guard.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/flow-84/n8n-guard/issues"
15
+ },
16
+ "type": "module",
17
+ "bin": {
18
+ "n8n-guard": "dist/index.js"
19
+ },
20
+ "main": "dist/index.js",
21
+ "files": [
22
+ "dist",
23
+ "README.md",
24
+ "LICENSE"
25
+ ],
26
+ "engines": {
27
+ "node": ">=22"
28
+ },
29
+ "scripts": {
30
+ "build": "tsc -p tsconfig.json",
31
+ "prepublishOnly": "npm run build",
32
+ "start": "node dist/index.js",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "test": "vitest run test/unit",
35
+ "test:integration": "vitest run test/integration",
36
+ "test:all": "vitest run"
37
+ },
38
+ "keywords": [
39
+ "mcp",
40
+ "n8n",
41
+ "monitoring",
42
+ "sre",
43
+ "model-context-protocol"
44
+ ],
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.20.0",
47
+ "pg": "^8.13.1",
48
+ "zod": "^3.25.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^22.10.0",
52
+ "@types/pg": "^8.11.10",
53
+ "typescript": "^5.7.0",
54
+ "vitest": "^2.1.8"
55
+ }
56
+ }