@falzz1010/laravel-mcp-server 1.0.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/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.md +866 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +101 -0
- package/build/index.js.map +1 -0
- package/build/prompts/laravel-prompts.d.ts +4 -0
- package/build/prompts/laravel-prompts.d.ts.map +1 -0
- package/build/prompts/laravel-prompts.js +81 -0
- package/build/prompts/laravel-prompts.js.map +1 -0
- package/build/resources/laravel-info.d.ts +4 -0
- package/build/resources/laravel-info.d.ts.map +1 -0
- package/build/resources/laravel-info.js +99 -0
- package/build/resources/laravel-info.js.map +1 -0
- package/build/tests/full-security-audit.test.d.ts +2 -0
- package/build/tests/full-security-audit.test.d.ts.map +1 -0
- package/build/tests/full-security-audit.test.js +226 -0
- package/build/tests/full-security-audit.test.js.map +1 -0
- package/build/tests/security.test.d.ts +2 -0
- package/build/tests/security.test.d.ts.map +1 -0
- package/build/tests/security.test.js +124 -0
- package/build/tests/security.test.js.map +1 -0
- package/build/tools/list-routes.d.ts +4 -0
- package/build/tools/list-routes.d.ts.map +1 -0
- package/build/tools/list-routes.js +68 -0
- package/build/tools/list-routes.js.map +1 -0
- package/build/tools/read-env.d.ts +4 -0
- package/build/tools/read-env.d.ts.map +1 -0
- package/build/tools/read-env.js +51 -0
- package/build/tools/read-env.js.map +1 -0
- package/build/tools/read-file.d.ts +4 -0
- package/build/tools/read-file.d.ts.map +1 -0
- package/build/tools/read-file.js +60 -0
- package/build/tools/read-file.js.map +1 -0
- package/build/tools/read-logs.d.ts +4 -0
- package/build/tools/read-logs.d.ts.map +1 -0
- package/build/tools/read-logs.js +60 -0
- package/build/tools/read-logs.js.map +1 -0
- package/build/tools/run-artisan.d.ts +5 -0
- package/build/tools/run-artisan.d.ts.map +1 -0
- package/build/tools/run-artisan.js +130 -0
- package/build/tools/run-artisan.js.map +1 -0
- package/build/tools/run-tinker.d.ts +4 -0
- package/build/tools/run-tinker.d.ts.map +1 -0
- package/build/tools/run-tinker.js +75 -0
- package/build/tools/run-tinker.js.map +1 -0
- package/build/tools/write-file.d.ts +4 -0
- package/build/tools/write-file.d.ts.map +1 -0
- package/build/tools/write-file.js +103 -0
- package/build/tools/write-file.js.map +1 -0
- package/build/utils/audit.d.ts +23 -0
- package/build/utils/audit.d.ts.map +1 -0
- package/build/utils/audit.js +52 -0
- package/build/utils/audit.js.map +1 -0
- package/build/utils/config.d.ts +12 -0
- package/build/utils/config.d.ts.map +1 -0
- package/build/utils/config.js +63 -0
- package/build/utils/config.js.map +1 -0
- package/build/utils/file.d.ts +8 -0
- package/build/utils/file.d.ts.map +1 -0
- package/build/utils/file.js +96 -0
- package/build/utils/file.js.map +1 -0
- package/build/utils/process.d.ts +16 -0
- package/build/utils/process.d.ts.map +1 -0
- package/build/utils/process.js +78 -0
- package/build/utils/process.js.map +1 -0
- package/build/utils/rate-limiter.d.ts +19 -0
- package/build/utils/rate-limiter.d.ts.map +1 -0
- package/build/utils/rate-limiter.js +57 -0
- package/build/utils/rate-limiter.js.map +1 -0
- package/build/utils/security.d.ts +23 -0
- package/build/utils/security.d.ts.map +1 -0
- package/build/utils/security.js +282 -0
- package/build/utils/security.js.map +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sanitizeTinkerCode } from "../utils/security.js";
|
|
3
|
+
import { runArtisan } from "../utils/process.js";
|
|
4
|
+
import { logAudit } from "../utils/audit.js";
|
|
5
|
+
export function registerRunTinkerTool(server, config) {
|
|
6
|
+
if (!config.allowTinker) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
server.registerTool("run_tinker", {
|
|
10
|
+
title: "Run Laravel Tinker PHP Code (Opt-In)",
|
|
11
|
+
description: "Executes a PHP snippet in Laravel Tinker context with strict security filtering.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
code: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe("PHP code snippet to execute (e.g. 'User::count();', 'config(\"app.name\");')"),
|
|
16
|
+
}),
|
|
17
|
+
}, async ({ code }) => {
|
|
18
|
+
// 1. Sanitize code
|
|
19
|
+
const check = sanitizeTinkerCode(code);
|
|
20
|
+
if (!check.safe) {
|
|
21
|
+
logAudit({
|
|
22
|
+
tool: "run_tinker",
|
|
23
|
+
tier: "DANGEROUS",
|
|
24
|
+
status: "BLOCKED",
|
|
25
|
+
reason: `Blocked functions: ${check.blocked.join(", ")}`,
|
|
26
|
+
});
|
|
27
|
+
return {
|
|
28
|
+
isError: true,
|
|
29
|
+
content: [
|
|
30
|
+
{
|
|
31
|
+
type: "text",
|
|
32
|
+
text: `[SECURITY BLOCK] Prohibited PHP functions/keywords detected: ${check.blocked.join(", ")}`,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Dry-run check
|
|
38
|
+
if (config.dryRun) {
|
|
39
|
+
logAudit({
|
|
40
|
+
tool: "run_tinker",
|
|
41
|
+
tier: "CAUTIOUS",
|
|
42
|
+
status: "ALLOWED",
|
|
43
|
+
reason: "DRY-RUN MODE",
|
|
44
|
+
});
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `[DRY-RUN PREVIEW] Would execute in tinker:\n${code}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// 2. Execute code via artisan tinker
|
|
55
|
+
// Note: Passing code via execute
|
|
56
|
+
const result = await runArtisan("tinker", ["--execute", code], {
|
|
57
|
+
laravelPath: config.laravelPath,
|
|
58
|
+
phpBinary: config.phpBinary,
|
|
59
|
+
timeoutMs: Math.min(config.commandTimeout, 10000), // Max 10s for tinker
|
|
60
|
+
});
|
|
61
|
+
logAudit({
|
|
62
|
+
tool: "run_tinker",
|
|
63
|
+
tier: "CAUTIOUS",
|
|
64
|
+
exitCode: result.exitCode,
|
|
65
|
+
outputSize: result.stdout.length,
|
|
66
|
+
status: result.exitCode === 0 ? "ALLOWED" : "ERROR",
|
|
67
|
+
duration: result.duration,
|
|
68
|
+
});
|
|
69
|
+
return {
|
|
70
|
+
isError: result.exitCode !== 0,
|
|
71
|
+
content: [{ type: "text", text: result.stdout || result.stderr || "(No output)" }],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=run-tinker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-tinker.js","sourceRoot":"","sources":["../../src/tools/run-tinker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,MAAoB;IAC3E,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QACxB,OAAO;IACT,CAAC;IAED,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,sCAAsC;QAC7C,WAAW,EACT,kFAAkF;QACpF,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,QAAQ,CAAC,8EAA8E,CAAC;SAC5F,CAAC;KACH,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE;QACjB,mBAAmB;QACnB,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAChB,QAAQ,CAAC;gBACP,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,WAAW;gBACjB,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,sBAAsB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aACzD,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,gEAAgE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;qBACjG;iBACF;aACF,CAAC;QACJ,CAAC;QAED,gBAAgB;QAChB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClB,QAAQ,CAAC;gBACP,IAAI,EAAE,YAAY;gBAClB,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,cAAc;aACvB,CAAC,CAAC;YACH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,+CAA+C,IAAI,EAAE;qBAC5D;iBACF;aACF,CAAC;QACJ,CAAC;QAED,qCAAqC;QACrC,iCAAiC;QACjC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,IAAI,CAAC,EAAE;YAC7D,WAAW,EAAE,MAAM,CAAC,WAAW;YAC/B,SAAS,EAAE,MAAM,CAAC,SAAS;YAC3B,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,EAAE,KAAK,CAAC,EAAE,qBAAqB;SACzE,CAAC,CAAC;QAEH,QAAQ,CAAC;YACP,IAAI,EAAE,YAAY;YAClB,IAAI,EAAE,UAAU;YAChB,QAAQ,EAAE,MAAM,CAAC,QAAQ;YACzB,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM;YAChC,MAAM,EAAE,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO;YACnD,QAAQ,EAAE,MAAM,CAAC,QAAQ;SAC1B,CAAC,CAAC;QAEH,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,QAAQ,KAAK,CAAC;YAC9B,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;SACnF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-file.d.ts","sourceRoot":"","sources":["../../src/tools/write-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA8GnF"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isPathSafe, isWriteAllowed, computeFileHash } from "../utils/security.js";
|
|
3
|
+
import { writeFileContent } from "../utils/file.js";
|
|
4
|
+
import { logAudit } from "../utils/audit.js";
|
|
5
|
+
export function registerWriteFileTool(server, config) {
|
|
6
|
+
if (!config.allowWrite) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
server.registerTool("write_file", {
|
|
10
|
+
title: "Write/Modify Laravel File (Opt-In)",
|
|
11
|
+
description: "Writes or edits a file within allowed Laravel project directories (app, routes, config, database, resources). Automatic backups are saved to .laravel-mcp-backup.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
path: z
|
|
14
|
+
.string()
|
|
15
|
+
.describe("Relative path to file from project root (e.g. 'app/Http/Controllers/ProductController.php')"),
|
|
16
|
+
content: z.string().describe("File content to write"),
|
|
17
|
+
}),
|
|
18
|
+
}, async ({ path: relPath, content }) => {
|
|
19
|
+
// 1. Max size check
|
|
20
|
+
if (content.length > 500 * 1024) {
|
|
21
|
+
logAudit({
|
|
22
|
+
tool: "write_file",
|
|
23
|
+
tier: "CAUTIOUS",
|
|
24
|
+
filePath: relPath,
|
|
25
|
+
status: "BLOCKED",
|
|
26
|
+
reason: "Content exceeds 500KB limit",
|
|
27
|
+
});
|
|
28
|
+
return {
|
|
29
|
+
isError: true,
|
|
30
|
+
content: [{ type: "text", text: "[SECURITY BLOCK] Content size exceeds 500KB limit." }],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// 2. Write allowed check
|
|
34
|
+
const writeCheck = isWriteAllowed(relPath);
|
|
35
|
+
if (!writeCheck.allowed) {
|
|
36
|
+
logAudit({
|
|
37
|
+
tool: "write_file",
|
|
38
|
+
tier: "CAUTIOUS",
|
|
39
|
+
filePath: relPath,
|
|
40
|
+
status: "BLOCKED",
|
|
41
|
+
reason: writeCheck.reason,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
isError: true,
|
|
45
|
+
content: [{ type: "text", text: `[SECURITY BLOCK] ${writeCheck.reason}` }],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
// 3. Path safe check
|
|
50
|
+
const safePath = isPathSafe(relPath, config.laravelPath);
|
|
51
|
+
// Dry-run check
|
|
52
|
+
if (config.dryRun) {
|
|
53
|
+
logAudit({
|
|
54
|
+
tool: "write_file",
|
|
55
|
+
tier: "CAUTIOUS",
|
|
56
|
+
filePath: relPath,
|
|
57
|
+
status: "ALLOWED",
|
|
58
|
+
reason: "DRY-RUN MODE",
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: "text",
|
|
64
|
+
text: `[DRY-RUN PREVIEW] Would write ${content.length} bytes to ${relPath}`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// 4. Write with backup
|
|
70
|
+
const { backupPath } = await writeFileContent(safePath, content, config.laravelPath);
|
|
71
|
+
const fileHash = computeFileHash(content);
|
|
72
|
+
logAudit({
|
|
73
|
+
tool: "write_file",
|
|
74
|
+
tier: "CAUTIOUS",
|
|
75
|
+
filePath: relPath,
|
|
76
|
+
fileHash,
|
|
77
|
+
outputSize: content.length,
|
|
78
|
+
status: "ALLOWED",
|
|
79
|
+
});
|
|
80
|
+
let msg = `Successfully wrote ${content.length} bytes to ${relPath}.`;
|
|
81
|
+
if (backupPath) {
|
|
82
|
+
msg += ` Previous version backed up to ${backupPath}.`;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: msg }],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logAudit({
|
|
90
|
+
tool: "write_file",
|
|
91
|
+
tier: "CAUTIOUS",
|
|
92
|
+
filePath: relPath,
|
|
93
|
+
status: "ERROR",
|
|
94
|
+
reason: err.message,
|
|
95
|
+
});
|
|
96
|
+
return {
|
|
97
|
+
isError: true,
|
|
98
|
+
content: [{ type: "text", text: `[WRITE ERROR] ${err.message}` }],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=write-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"write-file.js","sourceRoot":"","sources":["../../src/tools/write-file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface AuditEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
sessionId: string;
|
|
4
|
+
tool: string;
|
|
5
|
+
tier: string;
|
|
6
|
+
command?: string;
|
|
7
|
+
args?: string[];
|
|
8
|
+
filePath?: string;
|
|
9
|
+
exitCode?: number;
|
|
10
|
+
outputSize?: number;
|
|
11
|
+
fileHash?: string;
|
|
12
|
+
status: "ALLOWED" | "BLOCKED" | "ERROR";
|
|
13
|
+
reason?: string;
|
|
14
|
+
duration?: number;
|
|
15
|
+
}
|
|
16
|
+
export declare function initAuditLogger(laravelPath: string): string;
|
|
17
|
+
export declare function logAudit(entry: Omit<AuditEntry, "timestamp" | "sessionId">): void;
|
|
18
|
+
export declare function getAuditStats(): {
|
|
19
|
+
total: number;
|
|
20
|
+
allowed: number;
|
|
21
|
+
blocked: number;
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/utils/audit.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAmB3D;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,GAAG,WAAW,CAAC,GAAG,IAAI,CAuBjF;AAED,wBAAgB,aAAa,IAAI;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAEnF"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
let currentSessionId = "";
|
|
5
|
+
let auditFilePath = "";
|
|
6
|
+
let sessionStats = { total: 0, allowed: 0, blocked: 0 };
|
|
7
|
+
export function initAuditLogger(laravelPath) {
|
|
8
|
+
currentSessionId = crypto.randomBytes(6).toString("hex");
|
|
9
|
+
auditFilePath = path.join(laravelPath, ".laravel-mcp-audit.jsonl");
|
|
10
|
+
// Check file size for rotation if > 10MB
|
|
11
|
+
if (fs.existsSync(auditFilePath)) {
|
|
12
|
+
try {
|
|
13
|
+
const stats = fs.statSync(auditFilePath);
|
|
14
|
+
if (stats.size > 10 * 1024 * 1024) {
|
|
15
|
+
const dateStr = new Date().toISOString().split("T")[0];
|
|
16
|
+
const rotatedPath = path.join(laravelPath, `.laravel-mcp-audit.${dateStr}.jsonl`);
|
|
17
|
+
fs.renameSync(auditFilePath, rotatedPath);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Ignore rotation errors
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return currentSessionId;
|
|
25
|
+
}
|
|
26
|
+
export function logAudit(entry) {
|
|
27
|
+
sessionStats.total++;
|
|
28
|
+
if (entry.status === "ALLOWED") {
|
|
29
|
+
sessionStats.allowed++;
|
|
30
|
+
}
|
|
31
|
+
else if (entry.status === "BLOCKED") {
|
|
32
|
+
sessionStats.blocked++;
|
|
33
|
+
}
|
|
34
|
+
const fullEntry = {
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
sessionId: currentSessionId || "unknown",
|
|
37
|
+
...entry,
|
|
38
|
+
};
|
|
39
|
+
const line = JSON.stringify(fullEntry) + "\n";
|
|
40
|
+
if (auditFilePath) {
|
|
41
|
+
try {
|
|
42
|
+
fs.appendFileSync(auditFilePath, line, "utf-8");
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error("[AUDIT LOG ERROR]", err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function getAuditStats() {
|
|
50
|
+
return { ...sessionStats };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/utils/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,aAAa,CAAC;AAkBjC,IAAI,gBAAgB,GAAG,EAAE,CAAC;AAC1B,IAAI,aAAa,GAAG,EAAE,CAAC;AACvB,IAAI,YAAY,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AAExD,MAAM,UAAU,eAAe,CAAC,WAAmB;IACjD,gBAAgB,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACzD,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,0BAA0B,CAAC,CAAC;IAEnE,yCAAyC;IACzC,IAAI,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;YACzC,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;gBAClC,MAAM,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,sBAAsB,OAAO,QAAQ,CAAC,CAAC;gBAClF,EAAE,CAAC,UAAU,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;YAC5C,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;IACH,CAAC;IAED,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,KAAkD;IACzE,YAAY,CAAC,KAAK,EAAE,CAAC;IACrB,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,YAAY,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QACtC,YAAY,CAAC,OAAO,EAAE,CAAC;IACzB,CAAC;IAED,MAAM,SAAS,GAAe;QAC5B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,SAAS,EAAE,gBAAgB,IAAI,SAAS;QACxC,GAAG,KAAK;KACT,CAAC;IAEF,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IAE9C,IAAI,aAAa,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,EAAE,CAAC,cAAc,CAAC,aAAa,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,GAAG,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa;IAC3B,OAAO,EAAE,GAAG,YAAY,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ServerConfig {
|
|
2
|
+
laravelPath: string;
|
|
3
|
+
phpBinary: string;
|
|
4
|
+
commandTimeout: number;
|
|
5
|
+
maxLogLines: number;
|
|
6
|
+
allowWrite: boolean;
|
|
7
|
+
allowTinker: boolean;
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
rateLimit: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function parseConfig(argv: string[]): ServerConfig;
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,YAAY,CAwDxD"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
export function parseConfig(argv) {
|
|
3
|
+
const args = argv.slice(2);
|
|
4
|
+
let laravelPath = "";
|
|
5
|
+
let phpBinary = "php";
|
|
6
|
+
let commandTimeout = 30000;
|
|
7
|
+
let maxLogLines = 200;
|
|
8
|
+
let allowWrite = false;
|
|
9
|
+
let allowTinker = false;
|
|
10
|
+
let dryRun = false;
|
|
11
|
+
let rateLimit = 30;
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const arg = args[i];
|
|
14
|
+
if (arg === "--allow-write") {
|
|
15
|
+
allowWrite = true;
|
|
16
|
+
}
|
|
17
|
+
else if (arg === "--allow-tinker") {
|
|
18
|
+
allowTinker = true;
|
|
19
|
+
}
|
|
20
|
+
else if (arg === "--dry-run") {
|
|
21
|
+
dryRun = true;
|
|
22
|
+
}
|
|
23
|
+
else if (arg === "--php" && i + 1 < args.length) {
|
|
24
|
+
phpBinary = args[++i];
|
|
25
|
+
}
|
|
26
|
+
else if (arg === "--timeout" && i + 1 < args.length) {
|
|
27
|
+
const parsed = parseInt(args[++i], 10);
|
|
28
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
29
|
+
commandTimeout = parsed;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (arg === "--rate-limit" && i + 1 < args.length) {
|
|
33
|
+
const parsed = parseInt(args[++i], 10);
|
|
34
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
35
|
+
rateLimit = parsed;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
else if (arg === "--max-log-lines" && i + 1 < args.length) {
|
|
39
|
+
const parsed = parseInt(args[++i], 10);
|
|
40
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
41
|
+
maxLogLines = parsed;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (!arg.startsWith("--") && !laravelPath) {
|
|
45
|
+
laravelPath = path.resolve(arg);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (!laravelPath) {
|
|
49
|
+
// Default to current working directory if not supplied
|
|
50
|
+
laravelPath = process.cwd();
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
laravelPath,
|
|
54
|
+
phpBinary,
|
|
55
|
+
commandTimeout,
|
|
56
|
+
maxLogLines,
|
|
57
|
+
allowWrite,
|
|
58
|
+
allowTinker,
|
|
59
|
+
dryRun,
|
|
60
|
+
rateLimit,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAa7B,MAAM,UAAU,WAAW,CAAC,IAAc;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,WAAW,GAAG,EAAE,CAAC;IACrB,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,cAAc,GAAG,KAAK,CAAC;IAC3B,IAAI,WAAW,GAAG,GAAG,CAAC;IACtB,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,MAAM,GAAG,KAAK,CAAC;IACnB,IAAI,SAAS,GAAG,EAAE,CAAC;IAEnB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,GAAG,KAAK,eAAe,EAAE,CAAC;YAC5B,UAAU,GAAG,IAAI,CAAC;QACpB,CAAC;aAAM,IAAI,GAAG,KAAK,gBAAgB,EAAE,CAAC;YACpC,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;aAAM,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;YAC/B,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;aAAM,IAAI,GAAG,KAAK,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAClD,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QACxB,CAAC;aAAM,IAAI,GAAG,KAAK,WAAW,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACtD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjC,cAAc,GAAG,MAAM,CAAC;YAC1B,CAAC;QACH,CAAC;aAAM,IAAI,GAAG,KAAK,cAAc,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjC,SAAS,GAAG,MAAM,CAAC;YACrB,CAAC;QACH,CAAC;aAAM,IAAI,GAAG,KAAK,iBAAiB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;YAC5D,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjC,WAAW,GAAG,MAAM,CAAC;YACvB,CAAC;QACH,CAAC;aAAM,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACjD,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,uDAAuD;QACvD,WAAW,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9B,CAAC;IAED,OAAO;QACL,WAAW;QACX,SAAS;QACT,cAAc;QACd,WAAW;QACX,UAAU;QACV,WAAW;QACX,MAAM;QACN,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function findLogFile(laravelPath: string): Promise<string>;
|
|
2
|
+
export declare function readLastLines(filePath: string, lineCount?: number): Promise<string>;
|
|
3
|
+
export declare function readFileContent(filePath: string, maxSizeBytes?: number): Promise<string>;
|
|
4
|
+
export declare function createBackup(filePath: string, laravelPath: string): Promise<string | null>;
|
|
5
|
+
export declare function writeFileContent(filePath: string, content: string, laravelPath: string): Promise<{
|
|
6
|
+
backupPath: string | null;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBtE;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CA4C9F;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,GAAE,MAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAW3G;AAED,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAgBhG;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAcxC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function findLogFile(laravelPath) {
|
|
4
|
+
const logDir = path.join(laravelPath, "storage", "logs");
|
|
5
|
+
if (!fs.existsSync(logDir)) {
|
|
6
|
+
throw new Error(`Log directory does not exist: ${logDir}`);
|
|
7
|
+
}
|
|
8
|
+
// Check default laravel.log
|
|
9
|
+
const defaultLog = path.join(logDir, "laravel.log");
|
|
10
|
+
if (fs.existsSync(defaultLog)) {
|
|
11
|
+
return defaultLog;
|
|
12
|
+
}
|
|
13
|
+
// Find daily log files laravel-YYYY-MM-DD.log
|
|
14
|
+
const files = fs
|
|
15
|
+
.readdirSync(logDir)
|
|
16
|
+
.filter((f) => f.startsWith("laravel") && f.endsWith(".log"))
|
|
17
|
+
.sort()
|
|
18
|
+
.reverse();
|
|
19
|
+
if (files.length > 0) {
|
|
20
|
+
return path.join(logDir, files[0]);
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`No log files found in ${logDir}`);
|
|
23
|
+
}
|
|
24
|
+
export async function readLastLines(filePath, lineCount = 100) {
|
|
25
|
+
if (!fs.existsSync(filePath)) {
|
|
26
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
27
|
+
}
|
|
28
|
+
const limit = Math.min(Math.max(1, lineCount), 500);
|
|
29
|
+
// Read file in chunks from the end for efficiency
|
|
30
|
+
const stat = fs.statSync(filePath);
|
|
31
|
+
const fileSize = stat.size;
|
|
32
|
+
if (fileSize === 0) {
|
|
33
|
+
return "(Log file is empty)";
|
|
34
|
+
}
|
|
35
|
+
const chunkSize = 64 * 1024; // 64KB
|
|
36
|
+
const fd = fs.openSync(filePath, "r");
|
|
37
|
+
let lines = [];
|
|
38
|
+
let buffer = Buffer.alloc(chunkSize);
|
|
39
|
+
let readPosition = fileSize;
|
|
40
|
+
try {
|
|
41
|
+
let leftover = "";
|
|
42
|
+
while (readPosition > 0 && lines.length <= limit) {
|
|
43
|
+
const bytesToRead = Math.min(chunkSize, readPosition);
|
|
44
|
+
readPosition -= bytesToRead;
|
|
45
|
+
fs.readSync(fd, buffer, 0, bytesToRead, readPosition);
|
|
46
|
+
const chunkStr = buffer.toString("utf-8", 0, bytesToRead) + leftover;
|
|
47
|
+
const chunkLines = chunkStr.split(/\r?\n/);
|
|
48
|
+
leftover = chunkLines.shift() || "";
|
|
49
|
+
lines = [...chunkLines, ...lines];
|
|
50
|
+
}
|
|
51
|
+
if (leftover) {
|
|
52
|
+
lines.unshift(leftover);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
fs.closeSync(fd);
|
|
57
|
+
}
|
|
58
|
+
return lines.slice(-limit).join("\n");
|
|
59
|
+
}
|
|
60
|
+
export async function readFileContent(filePath, maxSizeBytes = 1024 * 1024) {
|
|
61
|
+
if (!fs.existsSync(filePath)) {
|
|
62
|
+
throw new Error(`File does not exist: ${filePath}`);
|
|
63
|
+
}
|
|
64
|
+
const stat = fs.statSync(filePath);
|
|
65
|
+
if (stat.size > maxSizeBytes) {
|
|
66
|
+
throw new Error(`File size (${(stat.size / 1024).toFixed(1)}KB) exceeds limit of ${(maxSizeBytes / 1024).toFixed(1)}KB.`);
|
|
67
|
+
}
|
|
68
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
69
|
+
}
|
|
70
|
+
export async function createBackup(filePath, laravelPath) {
|
|
71
|
+
if (!fs.existsSync(filePath)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const backupDir = path.join(laravelPath, ".laravel-mcp-backup");
|
|
75
|
+
if (!fs.existsSync(backupDir)) {
|
|
76
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
77
|
+
}
|
|
78
|
+
const relName = path.relative(laravelPath, filePath).replace(/[\\/]/g, "_");
|
|
79
|
+
const timestamp = Date.now();
|
|
80
|
+
const backupPath = path.join(backupDir, `${relName}.${timestamp}.bak`);
|
|
81
|
+
fs.copyFileSync(filePath, backupPath);
|
|
82
|
+
return backupPath;
|
|
83
|
+
}
|
|
84
|
+
export async function writeFileContent(filePath, content, laravelPath) {
|
|
85
|
+
// Ensure parent directory exists
|
|
86
|
+
const parentDir = path.dirname(filePath);
|
|
87
|
+
if (!fs.existsSync(parentDir)) {
|
|
88
|
+
fs.mkdirSync(parentDir, { recursive: true });
|
|
89
|
+
}
|
|
90
|
+
// Create backup if target file exists
|
|
91
|
+
const backupPath = await createBackup(filePath, laravelPath);
|
|
92
|
+
// Write content
|
|
93
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
94
|
+
return { backupPath };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/utils/file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface ExecResult {
|
|
2
|
+
stdout: string;
|
|
3
|
+
stderr: string;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
duration: number;
|
|
6
|
+
killed: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface RunOptions {
|
|
9
|
+
laravelPath: string;
|
|
10
|
+
phpBinary: string;
|
|
11
|
+
timeoutMs: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function runArtisan(command: string, args: string[] | undefined, options: RunOptions): Promise<ExecResult>;
|
|
14
|
+
export declare function findPhpBinary(customPath?: string): Promise<string>;
|
|
15
|
+
export declare function killAllChildren(): void;
|
|
16
|
+
//# sourceMappingURL=process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.d.ts","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,YAAK,EACnB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,UAAU,CAAC,CAgErB;AAED,wBAAgB,aAAa,CAAC,UAAU,GAAE,MAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAczE;AAED,wBAAgB,eAAe,IAAI,IAAI,CAStC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const activeChildren = new Set();
|
|
4
|
+
export function runArtisan(command, args = [], options) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const startTime = Date.now();
|
|
7
|
+
const artisanPath = path.join(options.laravelPath, "artisan");
|
|
8
|
+
const fullArgs = [artisanPath, command, ...args];
|
|
9
|
+
let killed = false;
|
|
10
|
+
// Filter environment to strip sensitive process environment variables
|
|
11
|
+
const safeEnv = { ...process.env };
|
|
12
|
+
delete safeEnv.APP_KEY;
|
|
13
|
+
delete safeEnv.DB_PASSWORD;
|
|
14
|
+
delete safeEnv.AWS_SECRET_ACCESS_KEY;
|
|
15
|
+
const child = execFile(options.phpBinary, fullArgs, {
|
|
16
|
+
cwd: options.laravelPath,
|
|
17
|
+
timeout: options.timeoutMs,
|
|
18
|
+
maxBuffer: 5 * 1024 * 1024, // 5MB limit
|
|
19
|
+
env: safeEnv,
|
|
20
|
+
}, (error, stdout, stderr) => {
|
|
21
|
+
activeChildren.delete(child);
|
|
22
|
+
const duration = Date.now() - startTime;
|
|
23
|
+
let exitCode = 0;
|
|
24
|
+
if (error) {
|
|
25
|
+
exitCode = typeof error.code === "number" ? error.code : 1;
|
|
26
|
+
if (error.killed) {
|
|
27
|
+
killed = true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Truncate output if larger than 50KB to protect LLM context
|
|
31
|
+
const maxCharOutput = 50 * 1024;
|
|
32
|
+
let finalStdout = stdout || "";
|
|
33
|
+
let finalStderr = stderr || "";
|
|
34
|
+
if (finalStdout.length > maxCharOutput) {
|
|
35
|
+
finalStdout =
|
|
36
|
+
finalStdout.substring(0, maxCharOutput) +
|
|
37
|
+
"\n... [Output truncated by MCP Server because it exceeds 50KB]";
|
|
38
|
+
}
|
|
39
|
+
if (finalStderr.length > maxCharOutput) {
|
|
40
|
+
finalStderr =
|
|
41
|
+
finalStderr.substring(0, maxCharOutput) +
|
|
42
|
+
"\n... [Stderr truncated by MCP Server because it exceeds 50KB]";
|
|
43
|
+
}
|
|
44
|
+
resolve({
|
|
45
|
+
stdout: finalStdout,
|
|
46
|
+
stderr: finalStderr,
|
|
47
|
+
exitCode,
|
|
48
|
+
duration,
|
|
49
|
+
killed,
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
activeChildren.add(child);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
export function findPhpBinary(customPath = "php") {
|
|
56
|
+
return new Promise((resolve, reject) => {
|
|
57
|
+
execFile(customPath, ["-v"], (error, stdout) => {
|
|
58
|
+
if (error) {
|
|
59
|
+
reject(new Error(`PHP binary '${customPath}' not found or failed to execute. Make sure PHP is installed and in PATH.`));
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
resolve(customPath);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
export function killAllChildren() {
|
|
68
|
+
for (const child of activeChildren) {
|
|
69
|
+
try {
|
|
70
|
+
child.kill("SIGKILL");
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Ignore errors when killing
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
activeChildren.clear();
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=process.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process.js","sourceRoot":"","sources":["../../src/utils/process.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RateLimiterConfig {
|
|
2
|
+
maxRequestsPerMinute: number;
|
|
3
|
+
maxRequestsPerHour: number;
|
|
4
|
+
cooldownMs: number;
|
|
5
|
+
}
|
|
6
|
+
export declare class RateLimiter {
|
|
7
|
+
private config;
|
|
8
|
+
private timestamps;
|
|
9
|
+
private lastRequestTime;
|
|
10
|
+
constructor(config?: RateLimiterConfig);
|
|
11
|
+
checkRateLimit(): {
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
retryAfterMs?: number;
|
|
14
|
+
reason?: string;
|
|
15
|
+
};
|
|
16
|
+
recordRequest(): void;
|
|
17
|
+
reset(): void;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limiter.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,WAAW;IAKpB,OAAO,CAAC,MAAM;IAJhB,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,eAAe,CAAK;gBAGlB,MAAM,GAAE,iBAIf;IAGI,cAAc,IAAI;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IA0C9E,aAAa,IAAI,IAAI;IAMrB,KAAK,IAAI,IAAI;CAIrB"}
|