@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/LICENSE +21 -0
  3. package/README.md +866 -0
  4. package/build/index.d.ts +2 -0
  5. package/build/index.d.ts.map +1 -0
  6. package/build/index.js +101 -0
  7. package/build/index.js.map +1 -0
  8. package/build/prompts/laravel-prompts.d.ts +4 -0
  9. package/build/prompts/laravel-prompts.d.ts.map +1 -0
  10. package/build/prompts/laravel-prompts.js +81 -0
  11. package/build/prompts/laravel-prompts.js.map +1 -0
  12. package/build/resources/laravel-info.d.ts +4 -0
  13. package/build/resources/laravel-info.d.ts.map +1 -0
  14. package/build/resources/laravel-info.js +99 -0
  15. package/build/resources/laravel-info.js.map +1 -0
  16. package/build/tests/full-security-audit.test.d.ts +2 -0
  17. package/build/tests/full-security-audit.test.d.ts.map +1 -0
  18. package/build/tests/full-security-audit.test.js +226 -0
  19. package/build/tests/full-security-audit.test.js.map +1 -0
  20. package/build/tests/security.test.d.ts +2 -0
  21. package/build/tests/security.test.d.ts.map +1 -0
  22. package/build/tests/security.test.js +124 -0
  23. package/build/tests/security.test.js.map +1 -0
  24. package/build/tools/list-routes.d.ts +4 -0
  25. package/build/tools/list-routes.d.ts.map +1 -0
  26. package/build/tools/list-routes.js +68 -0
  27. package/build/tools/list-routes.js.map +1 -0
  28. package/build/tools/read-env.d.ts +4 -0
  29. package/build/tools/read-env.d.ts.map +1 -0
  30. package/build/tools/read-env.js +51 -0
  31. package/build/tools/read-env.js.map +1 -0
  32. package/build/tools/read-file.d.ts +4 -0
  33. package/build/tools/read-file.d.ts.map +1 -0
  34. package/build/tools/read-file.js +60 -0
  35. package/build/tools/read-file.js.map +1 -0
  36. package/build/tools/read-logs.d.ts +4 -0
  37. package/build/tools/read-logs.d.ts.map +1 -0
  38. package/build/tools/read-logs.js +60 -0
  39. package/build/tools/read-logs.js.map +1 -0
  40. package/build/tools/run-artisan.d.ts +5 -0
  41. package/build/tools/run-artisan.d.ts.map +1 -0
  42. package/build/tools/run-artisan.js +130 -0
  43. package/build/tools/run-artisan.js.map +1 -0
  44. package/build/tools/run-tinker.d.ts +4 -0
  45. package/build/tools/run-tinker.d.ts.map +1 -0
  46. package/build/tools/run-tinker.js +75 -0
  47. package/build/tools/run-tinker.js.map +1 -0
  48. package/build/tools/write-file.d.ts +4 -0
  49. package/build/tools/write-file.d.ts.map +1 -0
  50. package/build/tools/write-file.js +103 -0
  51. package/build/tools/write-file.js.map +1 -0
  52. package/build/utils/audit.d.ts +23 -0
  53. package/build/utils/audit.d.ts.map +1 -0
  54. package/build/utils/audit.js +52 -0
  55. package/build/utils/audit.js.map +1 -0
  56. package/build/utils/config.d.ts +12 -0
  57. package/build/utils/config.d.ts.map +1 -0
  58. package/build/utils/config.js +63 -0
  59. package/build/utils/config.js.map +1 -0
  60. package/build/utils/file.d.ts +8 -0
  61. package/build/utils/file.d.ts.map +1 -0
  62. package/build/utils/file.js +96 -0
  63. package/build/utils/file.js.map +1 -0
  64. package/build/utils/process.d.ts +16 -0
  65. package/build/utils/process.d.ts.map +1 -0
  66. package/build/utils/process.js +78 -0
  67. package/build/utils/process.js.map +1 -0
  68. package/build/utils/rate-limiter.d.ts +19 -0
  69. package/build/utils/rate-limiter.d.ts.map +1 -0
  70. package/build/utils/rate-limiter.js +57 -0
  71. package/build/utils/rate-limiter.js.map +1 -0
  72. package/build/utils/security.d.ts +23 -0
  73. package/build/utils/security.d.ts.map +1 -0
  74. package/build/utils/security.js +282 -0
  75. package/build/utils/security.js.map +1 -0
  76. package/package.json +61 -0
@@ -0,0 +1,124 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { classifyCommand, isCommandAllowed, sanitizeArgs, validateDangerousFlags, sanitizeTinkerCode, isPathSafe, isWriteAllowed, maskEnvValues, } from "../utils/security.js";
7
+ import { RateLimiter } from "../utils/rate-limiter.js";
8
+ import { parseConfig } from "../utils/config.js";
9
+ test("Command Classification & Safelist", () => {
10
+ assert.equal(classifyCommand("about"), "READ_ONLY");
11
+ assert.equal(classifyCommand("route:list"), "READ_ONLY");
12
+ assert.equal(classifyCommand("make:controller"), "CAUTIOUS");
13
+ assert.equal(classifyCommand("migrate"), "CAUTIOUS");
14
+ assert.equal(classifyCommand("migrate:fresh"), "DANGEROUS");
15
+ assert.equal(classifyCommand("db:wipe"), "DANGEROUS");
16
+ assert.equal(classifyCommand("down"), "DANGEROUS");
17
+ assert.equal(classifyCommand("unknown:command"), "DANGEROUS");
18
+ assert.equal(isCommandAllowed("about").allowed, true);
19
+ assert.equal(isCommandAllowed("migrate:fresh").allowed, false);
20
+ assert.equal(isCommandAllowed("down").allowed, false);
21
+ });
22
+ test("Input Sanitization & Injections", () => {
23
+ assert.deepEqual(sanitizeArgs(["ProductController", "--resource"]), [
24
+ "ProductController",
25
+ "--resource",
26
+ ]);
27
+ assert.throws(() => sanitizeArgs(["; rm -rf /"]), /Security Error/);
28
+ assert.throws(() => sanitizeArgs(["&& cat /etc/passwd"]), /Security Error/);
29
+ assert.throws(() => sanitizeArgs(["$(whoami)"]), /Security Error/);
30
+ assert.throws(() => sanitizeArgs(["`id`"]), /Security Error/);
31
+ assert.throws(() => sanitizeArgs(["../etc/passwd"]), /Security Error/);
32
+ assert.throws(() => sanitizeArgs(["arg > out.txt"]), /Security Error/);
33
+ // Long arg check
34
+ const longArg = "a".repeat(300);
35
+ assert.throws(() => sanitizeArgs([longArg]), /exceeds 255/);
36
+ // Too many args check
37
+ const manyArgs = Array(25).fill("arg");
38
+ assert.throws(() => sanitizeArgs(manyArgs), /Too many arguments/);
39
+ });
40
+ test("Dangerous Flags Check", () => {
41
+ assert.throws(() => validateDangerousFlags("migrate", ["--force"]), /--force/);
42
+ assert.throws(() => validateDangerousFlags("migrate", ["--seed"]), /--seed/);
43
+ assert.throws(() => validateDangerousFlags("db:seed", ["--drop-tables"]), /prohibited/i);
44
+ // Valid flags
45
+ assert.doesNotThrow(() => validateDangerousFlags("migrate", ["--pretend"]));
46
+ });
47
+ test("Tinker Code Sanitization", () => {
48
+ assert.equal(sanitizeTinkerCode("User::count();").safe, true);
49
+ assert.equal(sanitizeTinkerCode("config('app.name');").safe, true);
50
+ const check1 = sanitizeTinkerCode("exec('whoami');");
51
+ assert.equal(check1.safe, false);
52
+ assert.ok(check1.blocked.includes("exec"));
53
+ const check2 = sanitizeTinkerCode("DB::statement('DROP TABLE users');");
54
+ assert.equal(check2.safe, false);
55
+ assert.ok(check2.blocked.includes("DB::statement"));
56
+ const check3 = sanitizeTinkerCode("unlink('/tmp/file');");
57
+ assert.equal(check3.safe, false);
58
+ assert.ok(check3.blocked.includes("unlink"));
59
+ });
60
+ test("Path Security", () => {
61
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "mcp-test-"));
62
+ const appDir = path.join(tmpDir, "app");
63
+ fs.mkdirSync(appDir, { recursive: true });
64
+ assert.equal(isPathSafe("app/Models/User.php", tmpDir), path.resolve(tmpDir, "app/Models/User.php"));
65
+ assert.throws(() => isPathSafe("../../etc/passwd", tmpDir), /Security Error/);
66
+ // Clean up
67
+ fs.rmSync(tmpDir, { recursive: true, force: true });
68
+ });
69
+ test("Write Permission Check", () => {
70
+ assert.equal(isWriteAllowed("app/Http/Controllers/TestController.php").allowed, true);
71
+ assert.equal(isWriteAllowed("routes/api.php").allowed, true);
72
+ assert.equal(isWriteAllowed("database/migrations/2026_create_test.php").allowed, true);
73
+ assert.equal(isWriteAllowed(".env").allowed, false);
74
+ assert.equal(isWriteAllowed("vendor/autoload.php").allowed, false);
75
+ assert.equal(isWriteAllowed("artisan").allowed, false);
76
+ assert.equal(isWriteAllowed("app/test.sh").allowed, false);
77
+ assert.equal(isWriteAllowed("public/index.php").allowed, false);
78
+ });
79
+ test("Environment Masking", () => {
80
+ const rawEnv = `
81
+ APP_NAME=Laravel
82
+ DB_PASSWORD=secret123
83
+ API_KEY=xyz_789
84
+ APP_URL=http://localhost
85
+ `;
86
+ const masked = maskEnvValues(rawEnv);
87
+ assert.ok(masked.includes("APP_NAME=Laravel"));
88
+ assert.ok(masked.includes("DB_PASSWORD=***MASKED***"));
89
+ assert.ok(masked.includes("API_KEY=***MASKED***"));
90
+ assert.ok(masked.includes("APP_URL=http://localhost"));
91
+ });
92
+ test("Rate Limiter", () => {
93
+ const limiter = new RateLimiter({
94
+ maxRequestsPerMinute: 3,
95
+ maxRequestsPerHour: 10,
96
+ cooldownMs: 0, // Disable cooldown for fast test
97
+ });
98
+ assert.equal(limiter.checkRateLimit().allowed, true);
99
+ limiter.recordRequest();
100
+ assert.equal(limiter.checkRateLimit().allowed, true);
101
+ limiter.recordRequest();
102
+ assert.equal(limiter.checkRateLimit().allowed, true);
103
+ limiter.recordRequest();
104
+ // 4th request should be blocked
105
+ assert.equal(limiter.checkRateLimit().allowed, false);
106
+ });
107
+ test("CLI Config Parsing", () => {
108
+ const config = parseConfig([
109
+ "node",
110
+ "index.js",
111
+ "/path/to/laravel",
112
+ "--allow-write",
113
+ "--allow-tinker",
114
+ "--dry-run",
115
+ "--timeout",
116
+ "15000",
117
+ ]);
118
+ assert.equal(config.laravelPath, path.resolve("/path/to/laravel"));
119
+ assert.equal(config.allowWrite, true);
120
+ assert.equal(config.allowTinker, true);
121
+ assert.equal(config.dryRun, true);
122
+ assert.equal(config.commandTimeout, 15000);
123
+ });
124
+ //# sourceMappingURL=security.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.test.js","sourceRoot":"","sources":["../../src/tests/security.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ServerConfig } from "../utils/config.js";
3
+ export declare function registerListRoutesTool(server: McpServer, config: ServerConfig): void;
4
+ //# sourceMappingURL=list-routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-routes.d.ts","sourceRoot":"","sources":["../../src/tools/list-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIlD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA4EpF"}
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import { runArtisan } from "../utils/process.js";
3
+ import { logAudit } from "../utils/audit.js";
4
+ export function registerListRoutesTool(server, config) {
5
+ server.registerTool("list_routes", {
6
+ title: "List Laravel Routes",
7
+ description: "Fetches defined application routes using 'php artisan route:list --json' with optional method/path filtering.",
8
+ inputSchema: z.object({
9
+ method: z
10
+ .string()
11
+ .optional()
12
+ .describe("Filter routes by HTTP method (e.g. 'GET', 'POST', 'PUT', 'DELETE')"),
13
+ path: z
14
+ .string()
15
+ .optional()
16
+ .describe("Filter routes by URI path pattern (e.g. 'api/', 'users')"),
17
+ }),
18
+ }, async ({ method, path: pathPattern }) => {
19
+ const result = await runArtisan("route:list", ["--json"], {
20
+ laravelPath: config.laravelPath,
21
+ phpBinary: config.phpBinary,
22
+ timeoutMs: config.commandTimeout,
23
+ });
24
+ if (result.exitCode !== 0) {
25
+ logAudit({
26
+ tool: "list_routes",
27
+ tier: "READ_ONLY",
28
+ status: "ERROR",
29
+ reason: result.stderr,
30
+ });
31
+ return {
32
+ isError: true,
33
+ content: [{ type: "text", text: `[ERROR] Failed to fetch route list: ${result.stderr}` }],
34
+ };
35
+ }
36
+ try {
37
+ let routes = JSON.parse(result.stdout);
38
+ if (method) {
39
+ const upperMethod = method.toUpperCase();
40
+ routes = routes.filter((r) => r.method ? r.method.toUpperCase().includes(upperMethod) : false);
41
+ }
42
+ if (pathPattern) {
43
+ const lowerPattern = pathPattern.toLowerCase();
44
+ routes = routes.filter((r) => r.uri ? r.uri.toLowerCase().includes(lowerPattern) : false);
45
+ }
46
+ logAudit({
47
+ tool: "list_routes",
48
+ tier: "READ_ONLY",
49
+ outputSize: JSON.stringify(routes).length,
50
+ status: "ALLOWED",
51
+ });
52
+ return {
53
+ content: [
54
+ {
55
+ type: "text",
56
+ text: JSON.stringify(routes, null, 2),
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ catch {
62
+ return {
63
+ content: [{ type: "text", text: result.stdout }],
64
+ };
65
+ }
66
+ });
67
+ }
68
+ //# sourceMappingURL=list-routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-routes.js","sourceRoot":"","sources":["../../src/tools/list-routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ServerConfig } from "../utils/config.js";
3
+ export declare function registerReadEnvTool(server: McpServer, config: ServerConfig): void;
4
+ //# sourceMappingURL=read-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-env.d.ts","sourceRoot":"","sources":["../../src/tools/read-env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CAmDjF"}
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import path from "node:path";
3
+ import { readFileContent } from "../utils/file.js";
4
+ import { maskEnvValues } from "../utils/security.js";
5
+ import { logAudit } from "../utils/audit.js";
6
+ export function registerReadEnvTool(server, config) {
7
+ server.registerTool("read_env", {
8
+ title: "Read Laravel .env Configuration",
9
+ description: "Reads the project .env file. Sensitive credentials (passwords, keys, secrets) are masked by default.",
10
+ inputSchema: z.object({
11
+ show_values: z
12
+ .boolean()
13
+ .optional()
14
+ .default(false)
15
+ .describe("If true, shows unmasked sensitive values (use with caution)"),
16
+ }),
17
+ }, async ({ show_values = false }) => {
18
+ const envPath = path.join(config.laravelPath, ".env");
19
+ try {
20
+ const rawContent = await readFileContent(envPath);
21
+ const finalContent = show_values ? rawContent : maskEnvValues(rawContent);
22
+ logAudit({
23
+ tool: "read_env",
24
+ tier: "READ_ONLY",
25
+ filePath: envPath,
26
+ status: "ALLOWED",
27
+ });
28
+ return {
29
+ content: [
30
+ {
31
+ type: "text",
32
+ text: finalContent,
33
+ },
34
+ ],
35
+ };
36
+ }
37
+ catch (err) {
38
+ logAudit({
39
+ tool: "read_env",
40
+ tier: "READ_ONLY",
41
+ status: "ERROR",
42
+ reason: err.message,
43
+ });
44
+ return {
45
+ isError: true,
46
+ content: [{ type: "text", text: `[ERROR READING .ENV] ${err.message}` }],
47
+ };
48
+ }
49
+ });
50
+ }
51
+ //# sourceMappingURL=read-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-env.js","sourceRoot":"","sources":["../../src/tools/read-env.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,MAAoB;IACzE,MAAM,CAAC,YAAY,CACjB,UAAU,EACV;QACE,KAAK,EAAE,iCAAiC;QACxC,WAAW,EACT,sGAAsG;QACxG,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,WAAW,EAAE,CAAC;iBACX,OAAO,EAAE;iBACT,QAAQ,EAAE;iBACV,OAAO,CAAC,KAAK,CAAC;iBACd,QAAQ,CAAC,6DAA6D,CAAC;SAC3E,CAAC;KACH,EACD,KAAK,EAAE,EAAE,WAAW,GAAG,KAAK,EAAE,EAAE,EAAE;QAChC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACtD,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC;YAClD,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAE1E,QAAQ,CAAC;gBACP,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,SAAS;aAClB,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,YAAY;qBACnB;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,QAAQ,CAAC;gBACP,IAAI,EAAE,UAAU;gBAChB,IAAI,EAAE,WAAW;gBACjB,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,GAAG,CAAC,OAAO;aACpB,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;aACzE,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ServerConfig } from "../utils/config.js";
3
+ export declare function registerReadFileTool(server: McpServer, config: ServerConfig): void;
4
+ //# sourceMappingURL=read-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-file.d.ts","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAKlD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA6DlF"}
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { isPathSafe } from "../utils/security.js";
3
+ import { readFileContent } from "../utils/file.js";
4
+ import { logAudit } from "../utils/audit.js";
5
+ export function registerReadFileTool(server, config) {
6
+ server.registerTool("read_file", {
7
+ title: "Read Laravel Source File",
8
+ description: "Reads content of a source file within the Laravel project with path traversal protection.",
9
+ inputSchema: z.object({
10
+ path: z
11
+ .string()
12
+ .describe("Relative path to file from project root (e.g. 'app/Models/User.php', 'routes/web.php')"),
13
+ }),
14
+ }, async ({ path: relPath }) => {
15
+ try {
16
+ if (relPath.includes(".env")) {
17
+ return {
18
+ isError: true,
19
+ content: [
20
+ {
21
+ type: "text",
22
+ text: "[SECURITY BLOCK] Use 'read_env' tool to read .env files with credential masking.",
23
+ },
24
+ ],
25
+ };
26
+ }
27
+ const safePath = isPathSafe(relPath, config.laravelPath);
28
+ const content = await readFileContent(safePath);
29
+ logAudit({
30
+ tool: "read_file",
31
+ tier: "READ_ONLY",
32
+ filePath: relPath,
33
+ outputSize: content.length,
34
+ status: "ALLOWED",
35
+ });
36
+ return {
37
+ content: [
38
+ {
39
+ type: "text",
40
+ text: content,
41
+ },
42
+ ],
43
+ };
44
+ }
45
+ catch (err) {
46
+ logAudit({
47
+ tool: "read_file",
48
+ tier: "READ_ONLY",
49
+ filePath: relPath,
50
+ status: "BLOCKED",
51
+ reason: err.message,
52
+ });
53
+ return {
54
+ isError: true,
55
+ content: [{ type: "text", text: `[SECURITY/FILE ERROR] ${err.message}` }],
56
+ };
57
+ }
58
+ });
59
+ }
60
+ //# sourceMappingURL=read-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-file.js","sourceRoot":"","sources":["../../src/tools/read-file.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAoB;IAC1E,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EAAE,2FAA2F;QACxG,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,QAAQ,CAAC,wFAAwF,CAAC;SACtG,CAAC;KACH,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC7B,OAAO;oBACL,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,kFAAkF;yBACzF;qBACF;iBACF,CAAC;YACJ,CAAC;YAED,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;YACzD,MAAM,OAAO,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;YAEhD,QAAQ,CAAC;gBACP,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,OAAO;gBACjB,UAAU,EAAE,OAAO,CAAC,MAAM;gBAC1B,MAAM,EAAE,SAAS;aAClB,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,OAAO;qBACd;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,QAAQ,CAAC;gBACP,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,OAAO;gBACjB,MAAM,EAAE,SAAS;gBACjB,MAAM,EAAE,GAAG,CAAC,OAAO;aACpB,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,yBAAyB,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;aAC1E,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ServerConfig } from "../utils/config.js";
3
+ export declare function registerReadLogsTool(server: McpServer, config: ServerConfig): void;
4
+ //# sourceMappingURL=read-logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-logs.d.ts","sourceRoot":"","sources":["../../src/tools/read-logs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAIlD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,YAAY,GAAG,IAAI,CA+DlF"}
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { findLogFile, readLastLines } from "../utils/file.js";
3
+ import { logAudit } from "../utils/audit.js";
4
+ export function registerReadLogsTool(server, config) {
5
+ server.registerTool("read_logs", {
6
+ title: "Read Laravel Log File",
7
+ description: "Reads recent log entries from storage/logs/laravel.log or daily log file with optional line filtering.",
8
+ inputSchema: z.object({
9
+ lines: z
10
+ .number()
11
+ .optional()
12
+ .default(100)
13
+ .describe("Number of recent lines to read (default: 100, max: 500)"),
14
+ filter: z
15
+ .string()
16
+ .optional()
17
+ .describe("Optional keyword filter (e.g. 'ERROR', 'CRITICAL', 'Exception')"),
18
+ }),
19
+ }, async ({ lines = 100, filter }) => {
20
+ try {
21
+ const logFile = await findLogFile(config.laravelPath);
22
+ let content = await readLastLines(logFile, lines);
23
+ if (filter) {
24
+ const filterLower = filter.toLowerCase();
25
+ content = content
26
+ .split("\n")
27
+ .filter((l) => l.toLowerCase().includes(filterLower))
28
+ .join("\n");
29
+ }
30
+ logAudit({
31
+ tool: "read_logs",
32
+ tier: "READ_ONLY",
33
+ filePath: logFile,
34
+ outputSize: content.length,
35
+ status: "ALLOWED",
36
+ });
37
+ return {
38
+ content: [
39
+ {
40
+ type: "text",
41
+ text: content || `(No log lines matched filter '${filter}')`,
42
+ },
43
+ ],
44
+ };
45
+ }
46
+ catch (err) {
47
+ logAudit({
48
+ tool: "read_logs",
49
+ tier: "READ_ONLY",
50
+ status: "ERROR",
51
+ reason: err.message,
52
+ });
53
+ return {
54
+ isError: true,
55
+ content: [{ type: "text", text: `[ERROR READING LOGS] ${err.message}` }],
56
+ };
57
+ }
58
+ });
59
+ }
60
+ //# sourceMappingURL=read-logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read-logs.js","sourceRoot":"","sources":["../../src/tools/read-logs.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,MAAoB;IAC1E,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,uBAAuB;QAC9B,WAAW,EACT,wGAAwG;QAC1G,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;YACpB,KAAK,EAAE,CAAC;iBACL,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,OAAO,CAAC,GAAG,CAAC;iBACZ,QAAQ,CAAC,yDAAyD,CAAC;YACtE,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,iEAAiE,CAAC;SAC/E,CAAC;KACH,EACD,KAAK,EAAE,EAAE,KAAK,GAAG,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE;QAChC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YACtD,IAAI,OAAO,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAElD,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;gBACzC,OAAO,GAAG,OAAO;qBACd,KAAK,CAAC,IAAI,CAAC;qBACX,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;qBACpD,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;YAED,QAAQ,CAAC;gBACP,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,WAAW;gBACjB,QAAQ,EAAE,OAAO;gBACjB,UAAU,EAAE,OAAO,CAAC,MAAM;gBAC1B,MAAM,EAAE,SAAS;aAClB,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,OAAO,IAAI,iCAAiC,MAAM,IAAI;qBAC7D;iBACF;aACF,CAAC;QACJ,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,QAAQ,CAAC;gBACP,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,WAAW;gBACjB,MAAM,EAAE,OAAO;gBACf,MAAM,EAAE,GAAG,CAAC,OAAO;aACpB,CAAC,CAAC;YAEH,OAAO;gBACL,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,wBAAwB,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;aACzE,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ServerConfig } from "../utils/config.js";
3
+ import { RateLimiter } from "../utils/rate-limiter.js";
4
+ export declare function registerRunArtisanTool(server: McpServer, config: ServerConfig, rateLimiter: RateLimiter): void;
5
+ //# sourceMappingURL=run-artisan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-artisan.d.ts","sourceRoot":"","sources":["../../src/tools/run-artisan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAQlD,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAEvD,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,YAAY,EACpB,WAAW,EAAE,WAAW,GACvB,IAAI,CAwIN"}
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ import { isCommandAllowed, sanitizeArgs, validateDangerousFlags, } from "../utils/security.js";
3
+ import { runArtisan } from "../utils/process.js";
4
+ import { logAudit } from "../utils/audit.js";
5
+ export function registerRunArtisanTool(server, config, rateLimiter) {
6
+ server.registerTool("run_artisan", {
7
+ title: "Run Laravel Artisan Command",
8
+ description: "Executes safe php artisan commands in the target Laravel project. Destructive commands (migrate:fresh, db:wipe, down) are permanently blocked.",
9
+ inputSchema: z.object({
10
+ command: z
11
+ .string()
12
+ .describe("The artisan command to run (e.g. 'make:controller', 'route:list', 'migrate')"),
13
+ args: z
14
+ .array(z.string())
15
+ .optional()
16
+ .describe("Additional command arguments (e.g. ['ProductController', '--resource'])"),
17
+ }),
18
+ }, async ({ command, args = [] }) => {
19
+ // Step 1: Check rate limiter
20
+ const rateCheck = rateLimiter.checkRateLimit();
21
+ if (!rateCheck.allowed) {
22
+ logAudit({
23
+ tool: "run_artisan",
24
+ tier: "UNKNOWN",
25
+ command,
26
+ args,
27
+ status: "BLOCKED",
28
+ reason: rateCheck.reason,
29
+ });
30
+ return {
31
+ isError: true,
32
+ content: [{ type: "text", text: `[RATE LIMIT BLOCKED] ${rateCheck.reason}` }],
33
+ };
34
+ }
35
+ // Step 2: Check command security classification
36
+ const check = isCommandAllowed(command);
37
+ if (!check.allowed) {
38
+ logAudit({
39
+ tool: "run_artisan",
40
+ tier: check.tier,
41
+ command,
42
+ args,
43
+ status: "BLOCKED",
44
+ reason: check.reason,
45
+ });
46
+ return {
47
+ isError: true,
48
+ content: [{ type: "text", text: `[SECURITY BLOCK] ${check.reason}` }],
49
+ };
50
+ }
51
+ // Step 3: Sanitize args & check dangerous flags
52
+ let cleanArgs = [];
53
+ try {
54
+ cleanArgs = sanitizeArgs(args);
55
+ validateDangerousFlags(command, cleanArgs);
56
+ }
57
+ catch (err) {
58
+ logAudit({
59
+ tool: "run_artisan",
60
+ tier: check.tier,
61
+ command,
62
+ args,
63
+ status: "BLOCKED",
64
+ reason: err.message,
65
+ });
66
+ return {
67
+ isError: true,
68
+ content: [{ type: "text", text: `[SECURITY BLOCK] ${err.message}` }],
69
+ };
70
+ }
71
+ // Record rate limit request for non-read-only
72
+ if (check.tier !== "READ_ONLY") {
73
+ rateLimiter.recordRequest();
74
+ }
75
+ // Step 4: Dry-run check
76
+ if (config.dryRun) {
77
+ logAudit({
78
+ tool: "run_artisan",
79
+ tier: check.tier,
80
+ command,
81
+ args: cleanArgs,
82
+ status: "ALLOWED",
83
+ reason: "DRY-RUN MODE",
84
+ });
85
+ return {
86
+ content: [
87
+ {
88
+ type: "text",
89
+ text: `[DRY-RUN PREVIEW] Would execute: php artisan ${command} ${cleanArgs.join(" ")}`,
90
+ },
91
+ ],
92
+ };
93
+ }
94
+ // Step 5: Execute via execFile
95
+ const result = await runArtisan(command, cleanArgs, {
96
+ laravelPath: config.laravelPath,
97
+ phpBinary: config.phpBinary,
98
+ timeoutMs: config.commandTimeout,
99
+ });
100
+ // Step 6: Log audit
101
+ logAudit({
102
+ tool: "run_artisan",
103
+ tier: check.tier,
104
+ command,
105
+ args: cleanArgs,
106
+ exitCode: result.exitCode,
107
+ outputSize: result.stdout.length + result.stderr.length,
108
+ status: result.exitCode === 0 ? "ALLOWED" : "ERROR",
109
+ duration: result.duration,
110
+ reason: result.killed ? "Execution timed out" : undefined,
111
+ });
112
+ let output = "";
113
+ if (result.stdout) {
114
+ output += result.stdout;
115
+ }
116
+ if (result.stderr) {
117
+ if (output)
118
+ output += "\n--- STDERR ---\n";
119
+ output += result.stderr;
120
+ }
121
+ if (!output) {
122
+ output = "(Command executed successfully with no output)";
123
+ }
124
+ return {
125
+ isError: result.exitCode !== 0,
126
+ content: [{ type: "text", text: output }],
127
+ };
128
+ });
129
+ }
130
+ //# sourceMappingURL=run-artisan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-artisan.js","sourceRoot":"","sources":["../../src/tools/run-artisan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ServerConfig } from "../utils/config.js";
3
+ export declare function registerRunTinkerTool(server: McpServer, config: ServerConfig): void;
4
+ //# sourceMappingURL=run-tinker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-tinker.d.ts","sourceRoot":"","sources":["../../src/tools/run-tinker.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,CAgFnF"}