@mokoconsulting/mokomysql-mcp 0.0.0-stage → 2.5.4

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,97 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ *
3
+ * This file is part of a Moko Consulting project.
4
+ *
5
+ * SPDX-License-Identifier: GPL-3.0-or-later
6
+ *
7
+ * FILE INFORMATION
8
+ * DEFGROUP: mokomysql.Config
9
+ * INGROUP: mokomysql
10
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mokomysql-mcp
11
+ * PATH: /src/config.ts
12
+ * VERSION: 01.00.00
13
+ * BRIEF: Configuration loader for MySQL MCP connections
14
+ */
15
+
16
+ import { readFile } from 'node:fs/promises';
17
+ import { resolve } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import type { MySqlConfig, MySqlConnection } from './types.js';
20
+ import { resolveSecretRefs } from './secrets.js';
21
+
22
+ const CONFIG_FILENAME = '.mcp_mokomysql.json';
23
+
24
+ /** Absolute path to the active config file (`MYSQL_MCP_CONFIG` or ~/.mcp_mokomysql.json). */
25
+ export function getConfigPath(): string {
26
+ return process.env.MYSQL_MCP_CONFIG
27
+ ? resolve(process.env.MYSQL_MCP_CONFIG)
28
+ : resolve(homedir(), CONFIG_FILENAME);
29
+ }
30
+
31
+ /**
32
+ * Absolute path to the sidecar state file — the MCP-managed learned-marker store.
33
+ * `MYSQL_MCP_STATE` overrides it outright; otherwise it sits beside the config as
34
+ * `<config>.state.json`, so learned state always follows the config that produced it.
35
+ */
36
+ export function getStatePath(): string {
37
+ if (process.env.MYSQL_MCP_STATE) return resolve(process.env.MYSQL_MCP_STATE);
38
+ return getConfigPath().replace(/\.json$/i, '') + '.state.json';
39
+ }
40
+
41
+ export async function loadConfig(): Promise<MySqlConfig> {
42
+ const config_path = getConfigPath();
43
+
44
+ try {
45
+ const raw = await readFile(config_path, 'utf-8');
46
+ const parsed = JSON.parse(raw) as Partial<MySqlConfig>;
47
+
48
+ // Resolve any "file:<path>" secret references (e.g. passwords in ~/.ssh/tokens).
49
+ await resolveSecretRefs(parsed);
50
+
51
+ if (!parsed.connections || Object.keys(parsed.connections).length === 0) {
52
+ throw new Error('No connections defined in config');
53
+ }
54
+
55
+ return {
56
+ connections: parsed.connections,
57
+ defaultConnection: parsed.defaultConnection ?? Object.keys(parsed.connections)[0],
58
+ };
59
+ } catch (err) {
60
+ const message = err instanceof Error ? err.message : String(err);
61
+ throw new Error(
62
+ `Failed to load config from ${config_path}: ${message}\n` +
63
+ `Create ${config_path} — see config.example.json for format.`,
64
+ );
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Normalize a name for tag matching: lowercase and strip every character
70
+ * that isn't a letter or digit. This makes "moko.live", "moko-live",
71
+ * "Moko Live" and "mokolive" all collapse to the same key.
72
+ */
73
+ export function normalizeTag(value: string): string {
74
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, '');
75
+ }
76
+
77
+ export function getConnection(config: MySqlConfig, name?: string): { key: string; conn: MySqlConnection } {
78
+ const requested = name ?? config.defaultConnection;
79
+
80
+ // 1. Exact key match — fast path, preserves prior behavior exactly.
81
+ const direct = config.connections[requested];
82
+ if (direct) return { key: requested, conn: direct };
83
+
84
+ // 2. Normalized match against connection keys and their tag shortcuts.
85
+ // Returns the canonical key, never the alias/tag the caller typed.
86
+ const target = normalizeTag(requested);
87
+ for (const [key, conn] of Object.entries(config.connections)) {
88
+ if (normalizeTag(key) === target) return { key, conn };
89
+ if (conn.tags?.some((tag) => normalizeTag(tag) === target)) return { key, conn };
90
+ }
91
+
92
+ // 3. Not found — list keys plus their tags so the caller can self-correct.
93
+ const available = Object.entries(config.connections)
94
+ .map(([key, conn]) => (conn.tags?.length ? `${key} [${conn.tags.join(', ')}]` : key))
95
+ .join(', ');
96
+ throw new Error(`Connection "${requested}" not found. Available: ${available}`);
97
+ }