xampp-mcp 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,57 @@
1
+ import path from "node:path";
2
+ import { exportDatabase } from "../runtime/mysqlClient.js";
3
+ import { toToolTextResult } from "../runtime/errors.js";
4
+ import { getBoolean, getOptionalString, getString } from "../runtime/validators.js";
5
+ import { assertDatabaseIdentifier, requireConfirmation } from "../security/policy.js";
6
+ import { mysqlConnectionFromArgs } from "./shared.js";
7
+ function defaultExportPath(database) {
8
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
9
+ return path.join(process.cwd(), `${database}-${timestamp}.sql`);
10
+ }
11
+ export function createDbExportTool(environment) {
12
+ return {
13
+ name: "db_export",
14
+ description: "Exports a MySQL database to a SQL file",
15
+ inputSchema: {
16
+ type: "object",
17
+ properties: {
18
+ database: { type: "string" },
19
+ outputPath: { type: "string" },
20
+ includeCreateDatabase: { type: "boolean" },
21
+ addDropTable: { type: "boolean" },
22
+ confirmed: { type: "boolean" },
23
+ host: { type: "string" },
24
+ port: { type: "number" },
25
+ user: { type: "string" },
26
+ password: { type: "string" },
27
+ },
28
+ required: ["database", "confirmed"],
29
+ additionalProperties: false,
30
+ },
31
+ annotations: {
32
+ title: "Database Export",
33
+ destructiveHint: false,
34
+ openWorldHint: false,
35
+ },
36
+ handler: async (args) => {
37
+ const database = getString(args.database, "database");
38
+ const outputPath = getOptionalString(args.outputPath, "outputPath") ?? defaultExportPath(database);
39
+ const includeCreateDatabase = getBoolean(args.includeCreateDatabase, "includeCreateDatabase", true);
40
+ const addDropTable = getBoolean(args.addDropTable, "addDropTable", true);
41
+ const confirmed = getBoolean(args.confirmed, "confirmed");
42
+ requireConfirmation(confirmed, "db_export");
43
+ assertDatabaseIdentifier(database, "database");
44
+ await exportDatabase(environment, {
45
+ ...mysqlConnectionFromArgs(args),
46
+ database,
47
+ outputPath,
48
+ includeCreateDatabase,
49
+ addDropTable,
50
+ });
51
+ return toToolTextResult(`Database ${database} exported to ${outputPath}`, {
52
+ database,
53
+ outputPath,
54
+ });
55
+ },
56
+ };
57
+ }
@@ -0,0 +1,46 @@
1
+ import { importDatabase } from "../runtime/mysqlClient.js";
2
+ import { toToolTextResult } from "../runtime/errors.js";
3
+ import { getBoolean, getString } from "../runtime/validators.js";
4
+ import { assertDatabaseIdentifier, requireConfirmation } from "../security/policy.js";
5
+ import { mysqlConnectionFromArgs } from "./shared.js";
6
+ export function createDbImportTool(environment) {
7
+ return {
8
+ name: "db_import",
9
+ description: "Imports SQL file into a MySQL database",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ database: { type: "string" },
14
+ inputPath: { type: "string" },
15
+ confirmed: { type: "boolean" },
16
+ host: { type: "string" },
17
+ port: { type: "number" },
18
+ user: { type: "string" },
19
+ password: { type: "string" },
20
+ },
21
+ required: ["database", "inputPath", "confirmed"],
22
+ additionalProperties: false,
23
+ },
24
+ annotations: {
25
+ title: "Database Import",
26
+ destructiveHint: true,
27
+ openWorldHint: false,
28
+ },
29
+ handler: async (args) => {
30
+ const database = getString(args.database, "database");
31
+ const inputPath = getString(args.inputPath, "inputPath");
32
+ const confirmed = getBoolean(args.confirmed, "confirmed");
33
+ requireConfirmation(confirmed, "db_import");
34
+ assertDatabaseIdentifier(database, "database");
35
+ await importDatabase(environment, {
36
+ ...mysqlConnectionFromArgs(args),
37
+ database,
38
+ inputPath,
39
+ });
40
+ return toToolTextResult(`Imported ${inputPath} into ${database}`, {
41
+ database,
42
+ inputPath,
43
+ });
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,89 @@
1
+ import { executeMysqlSql } from "../runtime/mysqlClient.js";
2
+ import { toToolTextResult } from "../runtime/errors.js";
3
+ import { getString } from "../runtime/validators.js";
4
+ import { assertDatabaseIdentifier, escapeSqlLiteral } from "../security/policy.js";
5
+ import { mysqlConnectionFromArgs } from "./shared.js";
6
+ export function createDbInspectTool(environment) {
7
+ return {
8
+ name: "db_inspect",
9
+ description: "Shows database status summary with table-level information",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ database: { type: "string" },
14
+ host: { type: "string" },
15
+ port: { type: "number" },
16
+ user: { type: "string" },
17
+ password: { type: "string" },
18
+ },
19
+ required: ["database"],
20
+ additionalProperties: false,
21
+ },
22
+ annotations: {
23
+ title: "Inspect Database",
24
+ readOnlyHint: true,
25
+ openWorldHint: false,
26
+ },
27
+ handler: async (args) => {
28
+ const database = getString(args.database, "database");
29
+ assertDatabaseIdentifier(database, "database");
30
+ const connection = mysqlConnectionFromArgs(args);
31
+ const literalDb = escapeSqlLiteral(database);
32
+ const schemaResult = await executeMysqlSql(environment, {
33
+ ...connection,
34
+ sql: `
35
+ SELECT
36
+ SCHEMA_NAME AS database_name,
37
+ DEFAULT_CHARACTER_SET_NAME AS charset_name,
38
+ DEFAULT_COLLATION_NAME AS collation_name
39
+ FROM information_schema.SCHEMATA
40
+ WHERE SCHEMA_NAME = ${literalDb}
41
+ `,
42
+ });
43
+ const summaryResult = await executeMysqlSql(environment, {
44
+ ...connection,
45
+ sql: `
46
+ SELECT
47
+ COUNT(*) AS table_count,
48
+ COALESCE(ROUND(SUM(TABLE_ROWS), 0), 0) AS estimated_rows,
49
+ COALESCE(ROUND(SUM(DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2), 0) AS total_size_mb
50
+ FROM information_schema.TABLES
51
+ WHERE TABLE_SCHEMA = ${literalDb}
52
+ `,
53
+ });
54
+ const tablesResult = await executeMysqlSql(environment, {
55
+ ...connection,
56
+ sql: `
57
+ SELECT
58
+ TABLE_NAME,
59
+ ENGINE,
60
+ TABLE_ROWS,
61
+ ROUND((DATA_LENGTH + INDEX_LENGTH) / 1024 / 1024, 2) AS size_mb,
62
+ CREATE_TIME,
63
+ UPDATE_TIME
64
+ FROM information_schema.TABLES
65
+ WHERE TABLE_SCHEMA = ${literalDb}
66
+ ORDER BY TABLE_NAME
67
+ `,
68
+ });
69
+ const text = [
70
+ `Database inspection: ${database}`,
71
+ "",
72
+ "Schema:",
73
+ schemaResult.stdout || "No schema metadata found",
74
+ "",
75
+ "Summary:",
76
+ summaryResult.stdout || "No summary available",
77
+ "",
78
+ "Tables:",
79
+ tablesResult.stdout || "No tables found",
80
+ ].join("\n");
81
+ return toToolTextResult(text, {
82
+ database,
83
+ schema: schemaResult.stdout,
84
+ summary: summaryResult.stdout,
85
+ tables: tablesResult.stdout,
86
+ });
87
+ },
88
+ };
89
+ }
@@ -0,0 +1,75 @@
1
+ import { executeMysqlSql } from "../runtime/mysqlClient.js";
2
+ import { toToolTextResult } from "../runtime/errors.js";
3
+ import { getBoolean, getString } from "../runtime/validators.js";
4
+ import { assertHost, assertDatabaseIdentifier, assertUsername, escapeSqlIdentifier, escapeSqlLiteral, requireConfirmation, } from "../security/policy.js";
5
+ import { mysqlConnectionFromArgs } from "./shared.js";
6
+ function normalizePrivileges(value) {
7
+ const trimmed = value.trim();
8
+ if (trimmed.length === 0) {
9
+ throw new Error("privileges cannot be empty");
10
+ }
11
+ if (trimmed === "*") {
12
+ return "ALL PRIVILEGES";
13
+ }
14
+ return trimmed
15
+ .split(",")
16
+ .map((item) => item.trim().toUpperCase())
17
+ .join(", ");
18
+ }
19
+ export function createGrantManageTool(environment) {
20
+ return {
21
+ name: "grant_manage",
22
+ description: "Grants privileges to a MySQL user on a database",
23
+ inputSchema: {
24
+ type: "object",
25
+ properties: {
26
+ database: { type: "string" },
27
+ username: { type: "string" },
28
+ hostScope: { type: "string" },
29
+ privileges: { type: "string", description: "Comma-separated list or * for all" },
30
+ withGrantOption: { type: "boolean" },
31
+ confirmed: { type: "boolean" },
32
+ host: { type: "string" },
33
+ port: { type: "number" },
34
+ user: { type: "string" },
35
+ password: { type: "string" },
36
+ },
37
+ required: ["database", "username", "privileges", "confirmed"],
38
+ additionalProperties: false,
39
+ },
40
+ annotations: {
41
+ title: "Grant Privileges",
42
+ destructiveHint: true,
43
+ openWorldHint: false,
44
+ },
45
+ handler: async (args) => {
46
+ const database = getString(args.database, "database");
47
+ const username = getString(args.username, "username");
48
+ const hostScope = getString(args.hostScope, "hostScope", { optional: true }) || "%";
49
+ const privileges = normalizePrivileges(getString(args.privileges, "privileges"));
50
+ const withGrantOption = getBoolean(args.withGrantOption, "withGrantOption", false);
51
+ const confirmed = getBoolean(args.confirmed, "confirmed");
52
+ requireConfirmation(confirmed, "grant_manage");
53
+ assertDatabaseIdentifier(database, "database");
54
+ assertUsername(username);
55
+ assertHost(hostScope);
56
+ const sql = [
57
+ `GRANT ${privileges} ON ${escapeSqlIdentifier(database)}.* TO ${escapeSqlLiteral(username)}@${escapeSqlLiteral(hostScope)}`,
58
+ withGrantOption ? "WITH GRANT OPTION" : "",
59
+ ]
60
+ .filter((part) => part.length > 0)
61
+ .join(" ");
62
+ await executeMysqlSql(environment, {
63
+ ...mysqlConnectionFromArgs(args),
64
+ sql,
65
+ });
66
+ return toToolTextResult(`Granted ${privileges} on ${database} to ${username}@${hostScope}`, {
67
+ database,
68
+ username,
69
+ hostScope,
70
+ privileges,
71
+ withGrantOption,
72
+ });
73
+ },
74
+ };
75
+ }
@@ -0,0 +1,41 @@
1
+ import { runCommand } from "../runtime/commandRunner.js";
2
+ import { toToolTextResult, ToolExecutionError } from "../runtime/errors.js";
3
+ import { getOptionalString, getString } from "../runtime/validators.js";
4
+ export function createPhpCliRunTool(environment) {
5
+ return {
6
+ name: "php_cli_run",
7
+ description: "Executes a PHP script using XAMPP PHP CLI",
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ scriptPath: { type: "string" },
12
+ args: { type: "string", description: "Arguments as a single string" },
13
+ },
14
+ required: ["scriptPath"],
15
+ additionalProperties: false,
16
+ },
17
+ annotations: {
18
+ title: "PHP CLI Run",
19
+ openWorldHint: false,
20
+ destructiveHint: false,
21
+ },
22
+ handler: async (args) => {
23
+ const scriptPath = getString(args.scriptPath, "scriptPath");
24
+ const rawArgs = getOptionalString(args.args, "args");
25
+ const cliArgs = [scriptPath, ...(rawArgs ? rawArgs.split(" ").filter((item) => item.length > 0) : [])];
26
+ const result = await runCommand({
27
+ command: environment.paths.phpExe,
28
+ args: cliArgs,
29
+ timeoutMs: 60_000,
30
+ });
31
+ if (result.exitCode !== 0) {
32
+ throw new ToolExecutionError(result.stderr || result.stdout || "PHP CLI failed", "PHP_CLI_FAILED");
33
+ }
34
+ const text = result.stdout.length > 0 ? result.stdout : "PHP script executed successfully";
35
+ return toToolTextResult(text, {
36
+ stdout: result.stdout,
37
+ stderr: result.stderr,
38
+ });
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,51 @@
1
+ import { getPathAvailability } from "../config/env.js";
2
+ import { runCommand } from "../runtime/commandRunner.js";
3
+ import { toToolTextResult } from "../runtime/errors.js";
4
+ async function checkPort(port) {
5
+ const result = await runCommand({
6
+ command: "netstat.exe",
7
+ args: ["-ano"],
8
+ timeoutMs: 10_000,
9
+ });
10
+ const pattern = new RegExp(`[:.]${port}\\s+`, "m");
11
+ return pattern.test(result.stdout);
12
+ }
13
+ export function createPreflightCheckTool(environment) {
14
+ return {
15
+ name: "preflight_check",
16
+ description: "Checks XAMPP binaries/scripts and common ports before operations",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ apachePort: { type: "number", default: 80 },
21
+ mysqlPort: { type: "number", default: 3306 },
22
+ },
23
+ additionalProperties: false,
24
+ },
25
+ annotations: {
26
+ title: "Preflight Check",
27
+ readOnlyHint: true,
28
+ openWorldHint: false,
29
+ },
30
+ handler: async (args) => {
31
+ const apachePort = typeof args.apachePort === "number" ? args.apachePort : 80;
32
+ const mysqlPort = typeof args.mysqlPort === "number" ? args.mysqlPort : 3306;
33
+ const [apacheBusy, mysqlBusy] = await Promise.all([checkPort(apachePort), checkPort(mysqlPort)]);
34
+ const paths = getPathAvailability(environment);
35
+ const allPathsAvailable = Object.values(paths).every((item) => item.exists);
36
+ const summary = [
37
+ `Path checks: ${allPathsAvailable ? "ok" : "missing files"}`,
38
+ `Apache port ${apachePort}: ${apacheBusy ? "in use" : "free"}`,
39
+ `MySQL port ${mysqlPort}: ${mysqlBusy ? "in use" : "free"}`,
40
+ ].join("\n");
41
+ return toToolTextResult(summary, {
42
+ allPathsAvailable,
43
+ ports: {
44
+ apache: { port: apachePort, inUse: apacheBusy },
45
+ mysql: { port: mysqlPort, inUse: mysqlBusy },
46
+ },
47
+ paths,
48
+ });
49
+ },
50
+ };
51
+ }
@@ -0,0 +1,46 @@
1
+ import { executeMysqlSql } from "../runtime/mysqlClient.js";
2
+ import { toToolTextResult } from "../runtime/errors.js";
3
+ import { getBoolean, getOptionalString, getString } from "../runtime/validators.js";
4
+ import { requireConfirmation } from "../security/policy.js";
5
+ import { mysqlConnectionFromArgs } from "./shared.js";
6
+ export function createQueryExecuteTool(environment) {
7
+ return {
8
+ name: "query_execute",
9
+ description: "Runs SQL query with write capability (INSERT/UPDATE/DELETE/DDL)",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ sql: { type: "string" },
14
+ database: { type: "string" },
15
+ confirmed: { type: "boolean" },
16
+ host: { type: "string" },
17
+ port: { type: "number" },
18
+ user: { type: "string" },
19
+ password: { type: "string" },
20
+ },
21
+ required: ["sql", "confirmed"],
22
+ additionalProperties: false,
23
+ },
24
+ annotations: {
25
+ title: "Execute Query",
26
+ destructiveHint: true,
27
+ openWorldHint: false,
28
+ },
29
+ handler: async (args) => {
30
+ const sql = getString(args.sql, "sql");
31
+ const database = getOptionalString(args.database, "database");
32
+ const confirmed = getBoolean(args.confirmed, "confirmed");
33
+ requireConfirmation(confirmed, "query_execute");
34
+ const result = await executeMysqlSql(environment, {
35
+ ...mysqlConnectionFromArgs(args),
36
+ database,
37
+ sql,
38
+ });
39
+ const text = result.stdout || "Query executed successfully";
40
+ return toToolTextResult(text, {
41
+ database,
42
+ stdout: result.stdout,
43
+ });
44
+ },
45
+ };
46
+ }
@@ -0,0 +1,50 @@
1
+ import { executeMysqlSql } from "../runtime/mysqlClient.js";
2
+ import { toToolTextResult } from "../runtime/errors.js";
3
+ import { getOptionalString, getString } from "../runtime/validators.js";
4
+ import { mysqlConnectionFromArgs } from "./shared.js";
5
+ const READ_ONLY_PREFIXES = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"];
6
+ function assertReadonlyQuery(sql) {
7
+ const normalized = sql.trim().replace(/\s+/g, " ").toUpperCase();
8
+ if (READ_ONLY_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
9
+ return;
10
+ }
11
+ throw new Error("query_readonly only accepts SELECT/SHOW/DESCRIBE/EXPLAIN statements");
12
+ }
13
+ export function createQueryReadonlyTool(environment) {
14
+ return {
15
+ name: "query_readonly",
16
+ description: "Runs read-only SQL query for diagnostics",
17
+ inputSchema: {
18
+ type: "object",
19
+ properties: {
20
+ sql: { type: "string" },
21
+ database: { type: "string" },
22
+ host: { type: "string" },
23
+ port: { type: "number" },
24
+ user: { type: "string" },
25
+ password: { type: "string" },
26
+ },
27
+ required: ["sql"],
28
+ additionalProperties: false,
29
+ },
30
+ annotations: {
31
+ title: "Read-only Query",
32
+ readOnlyHint: true,
33
+ openWorldHint: false,
34
+ },
35
+ handler: async (args) => {
36
+ const sql = getString(args.sql, "sql");
37
+ const database = getOptionalString(args.database, "database");
38
+ assertReadonlyQuery(sql);
39
+ const result = await executeMysqlSql(environment, {
40
+ ...mysqlConnectionFromArgs(args),
41
+ database,
42
+ sql,
43
+ });
44
+ const text = result.stdout || "Query executed successfully (no rows in output)";
45
+ return toToolTextResult(text, {
46
+ stdout: result.stdout,
47
+ });
48
+ },
49
+ };
50
+ }
@@ -0,0 +1,36 @@
1
+ import { createDbCreateTool } from "./dbCreate.js";
2
+ import { createDbExportTool } from "./dbExport.js";
3
+ import { createDbImportTool } from "./dbImport.js";
4
+ import { createDbInspectTool } from "./dbInspect.js";
5
+ import { createGrantManageTool } from "./grantManage.js";
6
+ import { createPhpCliRunTool } from "./phpCliRun.js";
7
+ import { createQueryExecuteTool } from "./queryExecute.js";
8
+ import { createPreflightCheckTool } from "./preflightCheck.js";
9
+ import { createQueryReadonlyTool } from "./queryReadonly.js";
10
+ import { createStackStatusTool } from "./stackStatus.js";
11
+ import { createTableCreateTool } from "./tableCreate.js";
12
+ import { createUserCreateTool } from "./userCreate.js";
13
+ export function createToolRegistry(environment) {
14
+ const tools = [
15
+ createPreflightCheckTool(environment),
16
+ createStackStatusTool(environment),
17
+ createQueryReadonlyTool(environment),
18
+ createQueryExecuteTool(environment),
19
+ createDbInspectTool(environment),
20
+ createDbCreateTool(environment),
21
+ createTableCreateTool(environment),
22
+ createUserCreateTool(environment),
23
+ createGrantManageTool(environment),
24
+ createDbExportTool(environment),
25
+ createDbImportTool(environment),
26
+ createPhpCliRunTool(environment),
27
+ ];
28
+ const toolMap = new Map();
29
+ for (const tool of tools) {
30
+ toolMap.set(tool.name, tool);
31
+ }
32
+ return {
33
+ list: () => tools,
34
+ get: (name) => toolMap.get(name),
35
+ };
36
+ }
@@ -0,0 +1,31 @@
1
+ import { existsSync } from "node:fs";
2
+ import { executeMysqlSql } from "../runtime/mysqlClient.js";
3
+ import { getOptionalNumber, getOptionalString } from "../runtime/validators.js";
4
+ export function getMode(args, environment) {
5
+ const mode = getOptionalString(args.mode, "mode");
6
+ if (mode === undefined) {
7
+ return environment.defaultMode;
8
+ }
9
+ return mode === "service" ? "service" : "console";
10
+ }
11
+ export function ensurePathExists(filePath, label) {
12
+ if (!existsSync(filePath)) {
13
+ throw new Error(`${label} not found: ${filePath}`);
14
+ }
15
+ }
16
+ export function mysqlConnectionFromArgs(args) {
17
+ return {
18
+ host: getOptionalString(args.host, "host"),
19
+ port: getOptionalNumber(args.port, "port"),
20
+ user: getOptionalString(args.user, "user"),
21
+ password: getOptionalString(args.password, "password"),
22
+ };
23
+ }
24
+ export async function runMysqlStatement(environment, args, sql, database) {
25
+ const connection = mysqlConnectionFromArgs(args);
26
+ await executeMysqlSql(environment, {
27
+ ...connection,
28
+ database,
29
+ sql,
30
+ });
31
+ }
@@ -0,0 +1,84 @@
1
+ import { getPathAvailability } from "../config/env.js";
2
+ import { runCommand } from "../runtime/commandRunner.js";
3
+ import { toToolTextResult } from "../runtime/errors.js";
4
+ async function isProcessRunning(imageName) {
5
+ const result = await runCommand({
6
+ command: "tasklist.exe",
7
+ args: ["/FI", `IMAGENAME eq ${imageName}`],
8
+ timeoutMs: 5_000,
9
+ });
10
+ return result.stdout.toLowerCase().includes(imageName.toLowerCase());
11
+ }
12
+ async function getServiceState(serviceName) {
13
+ const result = await runCommand({
14
+ command: "sc.exe",
15
+ args: ["query", serviceName],
16
+ timeoutMs: 5_000,
17
+ });
18
+ if (result.exitCode !== 0) {
19
+ return "not-found-or-no-access";
20
+ }
21
+ const lines = result.stdout.split(/\r?\n/);
22
+ const stateLine = lines.find((line) => line.includes("STATE"));
23
+ if (!stateLine) {
24
+ return "unknown";
25
+ }
26
+ return stateLine.split(":").at(-1)?.trim() ?? "unknown";
27
+ }
28
+ export function createStackStatusTool(environment) {
29
+ return {
30
+ name: "stack_status",
31
+ description: "Shows XAMPP path checks and Apache/MySQL process or service state",
32
+ inputSchema: {
33
+ type: "object",
34
+ properties: {},
35
+ additionalProperties: false,
36
+ },
37
+ annotations: {
38
+ title: "Stack Status",
39
+ readOnlyHint: true,
40
+ openWorldHint: false,
41
+ },
42
+ handler: async () => {
43
+ const [apacheRunning, mysqlRunning, apacheServiceState, mysqlServiceState] = await Promise.all([
44
+ isProcessRunning("httpd.exe"),
45
+ isProcessRunning("mysqld.exe"),
46
+ getServiceState(environment.apacheServiceName),
47
+ getServiceState(environment.mysqlServiceName),
48
+ ]);
49
+ const availability = getPathAvailability(environment);
50
+ const adviceLines = [];
51
+ if (!apacheRunning) {
52
+ adviceLines.push("Apache is stopped. Ask the user to start Apache from XAMPP Control Panel and confirm when ready before continuing. Do not run apache_start.bat automatically.");
53
+ }
54
+ if (!mysqlRunning) {
55
+ adviceLines.push("MySQL is stopped. Ask the user to start MySQL from XAMPP Control Panel and confirm when ready before continuing. Do not run mysql_start.bat automatically.");
56
+ }
57
+ const adviceText = adviceLines.length > 0 ? `\n\nAdvice:\n${adviceLines.join("\n")}` : "";
58
+ const summary = [
59
+ `XAMPP dir: ${environment.xamppDir}`,
60
+ `Apache process: ${apacheRunning ? "running" : "stopped"}`,
61
+ `MySQL process: ${mysqlRunning ? "running" : "stopped"}`,
62
+ `Apache service (${environment.apacheServiceName}): ${apacheServiceState}`,
63
+ `MySQL service (${environment.mysqlServiceName}): ${mysqlServiceState}`,
64
+ ].join("\n") + adviceText;
65
+ return toToolTextResult(summary, {
66
+ xamppDir: environment.xamppDir,
67
+ paths: availability,
68
+ processes: {
69
+ apache: apacheRunning,
70
+ mysql: mysqlRunning,
71
+ },
72
+ services: {
73
+ apache: apacheServiceState,
74
+ mysql: mysqlServiceState,
75
+ },
76
+ requiresUserAction: adviceLines.length > 0,
77
+ nextStep: adviceLines.length > 0
78
+ ? "Wait for user confirmation after modules are started in XAMPP Control Panel, then retry the requested tool."
79
+ : "No action needed.",
80
+ advice: adviceLines,
81
+ });
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,61 @@
1
+ import { executeMysqlSql } from "../runtime/mysqlClient.js";
2
+ import { toToolTextResult } from "../runtime/errors.js";
3
+ import { getBoolean, getString } from "../runtime/validators.js";
4
+ import { assertDatabaseIdentifier, assertIdentifier, requireConfirmation } from "../security/policy.js";
5
+ import { mysqlConnectionFromArgs } from "./shared.js";
6
+ function normalizeCreateStatement(statement, tableName) {
7
+ const compact = statement.trim();
8
+ const upper = compact.toUpperCase();
9
+ if (!upper.startsWith("CREATE TABLE")) {
10
+ throw new Error("createStatement must start with CREATE TABLE");
11
+ }
12
+ if (!upper.includes(tableName.toUpperCase())) {
13
+ throw new Error("createStatement must target the provided table");
14
+ }
15
+ return compact;
16
+ }
17
+ export function createTableCreateTool(environment) {
18
+ return {
19
+ name: "table_create",
20
+ description: "Creates a table using a CREATE TABLE statement",
21
+ inputSchema: {
22
+ type: "object",
23
+ properties: {
24
+ database: { type: "string" },
25
+ table: { type: "string" },
26
+ createStatement: { type: "string" },
27
+ confirmed: { type: "boolean" },
28
+ host: { type: "string" },
29
+ port: { type: "number" },
30
+ user: { type: "string" },
31
+ password: { type: "string" },
32
+ },
33
+ required: ["database", "table", "createStatement", "confirmed"],
34
+ additionalProperties: false,
35
+ },
36
+ annotations: {
37
+ title: "Create Table",
38
+ destructiveHint: true,
39
+ openWorldHint: false,
40
+ },
41
+ handler: async (args) => {
42
+ const database = getString(args.database, "database");
43
+ const table = getString(args.table, "table");
44
+ const createStatement = getString(args.createStatement, "createStatement");
45
+ const confirmed = getBoolean(args.confirmed, "confirmed");
46
+ requireConfirmation(confirmed, "table_create");
47
+ assertDatabaseIdentifier(database, "database");
48
+ assertIdentifier(table, "table");
49
+ const sql = normalizeCreateStatement(createStatement, table);
50
+ await executeMysqlSql(environment, {
51
+ ...mysqlConnectionFromArgs(args),
52
+ database,
53
+ sql,
54
+ });
55
+ return toToolTextResult(`Table ${database}.${table} created`, {
56
+ database,
57
+ table,
58
+ });
59
+ },
60
+ };
61
+ }