a11y-devkit-deploy 0.9.3 → 0.9.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.
- package/README.md +11 -4
- package/config/settings.json +2 -2
- package/package.json +1 -1
- package/src/installers/mcp.js +131 -4
package/README.md
CHANGED
|
@@ -218,14 +218,21 @@ Add an object to the `hostApplications` array with the host application's config
|
|
|
218
218
|
**Host Application Configuration Properties:**
|
|
219
219
|
- `id` - Unique identifier for the host application
|
|
220
220
|
- `displayName` - Human-readable name shown in prompts
|
|
221
|
-
- `mcpServerKey` - MCP config key name (`"servers"` or `"
|
|
221
|
+
- `mcpServerKey` - MCP config key name (`"servers"`, `"mcpServers"`, or `"mcp_servers"` for TOML)
|
|
222
222
|
- `skillsFolder` - Path to skills directory (relative to home/project root)
|
|
223
|
-
- `mcpConfigFile` - Path to MCP config file (relative to home/project root)
|
|
223
|
+
- `mcpConfigFile` - Path to MCP config file (relative to home/project root). Supports both JSON (`.json`) and TOML (`.toml`) formats. TOML format is auto-detected by file extension (used by Codex).
|
|
224
224
|
- `globalMcpConfigFile` - (Optional) Path to global MCP config relative to AppData/Application Support instead of home directory. Used for hosts like VSCode that store configs in platform-specific app directories:
|
|
225
225
|
- Windows: `%APPDATA%` (e.g., `C:\Users\name\AppData\Roaming`)
|
|
226
226
|
- macOS: `~/Library/Application Support`
|
|
227
227
|
- Linux: `$XDG_CONFIG_HOME` or `~/.config`
|
|
228
228
|
|
|
229
|
+
**Note:** Codex uses TOML format for its MCP configuration (`~/.codex/config.toml`), which requires the `mcpServerKey` to be `"mcp_servers"` and generates config entries like:
|
|
230
|
+
```toml
|
|
231
|
+
[mcp_servers.magentaa11y]
|
|
232
|
+
command = "npx"
|
|
233
|
+
args = ["-y", "magentaa11y-mcp"]
|
|
234
|
+
```
|
|
235
|
+
|
|
229
236
|
### Config Structure
|
|
230
237
|
|
|
231
238
|
- `skillsFolder` - Subfolder name to bundle skills under (e.g., "a11y")
|
|
@@ -255,7 +262,7 @@ your-project/
|
|
|
255
262
|
│ ├── mcp.json # Cursor MCP config
|
|
256
263
|
│ └── skills/ # Cursor skills
|
|
257
264
|
├── .codex/
|
|
258
|
-
│ ├──
|
|
265
|
+
│ ├── config.toml # Codex MCP config
|
|
259
266
|
│ └── skills/ # Codex skills
|
|
260
267
|
├── .github/
|
|
261
268
|
│ ├── mcp.json # VSCode MCP config
|
|
@@ -277,7 +284,7 @@ your-project/
|
|
|
277
284
|
├── mcp.json # Cursor global MCP config
|
|
278
285
|
└── skills/ # Cursor global skills
|
|
279
286
|
~/.codex/
|
|
280
|
-
├──
|
|
287
|
+
├── config.toml # Codex global MCP config
|
|
281
288
|
└── skills/ # Codex global skills
|
|
282
289
|
~/.github/
|
|
283
290
|
└── skills/ # VSCode global skills
|
package/config/settings.json
CHANGED
|
@@ -63,9 +63,9 @@
|
|
|
63
63
|
{
|
|
64
64
|
"id": "codex",
|
|
65
65
|
"displayName": "Codex",
|
|
66
|
-
"mcpServerKey": "
|
|
66
|
+
"mcpServerKey": "mcp_servers",
|
|
67
67
|
"skillsFolder": ".codex/skills",
|
|
68
|
-
"mcpConfigFile": ".codex/
|
|
68
|
+
"mcpConfigFile": ".codex/config.toml"
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"id": "vscode",
|
package/package.json
CHANGED
package/src/installers/mcp.js
CHANGED
|
@@ -10,6 +10,10 @@ async function pathExists(target) {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function isTomlFile(filePath) {
|
|
14
|
+
return filePath.endsWith('.toml');
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
async function loadJson(filePath) {
|
|
14
18
|
if (!(await pathExists(filePath))) {
|
|
15
19
|
return {};
|
|
@@ -25,6 +29,120 @@ async function loadJson(filePath) {
|
|
|
25
29
|
}
|
|
26
30
|
}
|
|
27
31
|
|
|
32
|
+
// Simple TOML parser for MCP server configs
|
|
33
|
+
function parseSimpleToml(content) {
|
|
34
|
+
const result = {};
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
let currentSection = null;
|
|
37
|
+
let currentTable = null;
|
|
38
|
+
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
const trimmed = line.trim();
|
|
41
|
+
|
|
42
|
+
// Skip empty lines and comments
|
|
43
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
44
|
+
|
|
45
|
+
// Parse table headers like [mcp_servers.name]
|
|
46
|
+
const tableMatch = trimmed.match(/^\[([^\]]+)\]$/);
|
|
47
|
+
if (tableMatch) {
|
|
48
|
+
const parts = tableMatch[1].split('.');
|
|
49
|
+
if (parts.length === 2) {
|
|
50
|
+
const [section, table] = parts;
|
|
51
|
+
if (!result[section]) result[section] = {};
|
|
52
|
+
if (!result[section][table]) result[section][table] = {};
|
|
53
|
+
currentSection = section;
|
|
54
|
+
currentTable = table;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Parse key-value pairs
|
|
60
|
+
const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
|
|
61
|
+
if (kvMatch && currentSection && currentTable) {
|
|
62
|
+
const [, key, value] = kvMatch;
|
|
63
|
+
|
|
64
|
+
// Parse arrays like ["a", "b"]
|
|
65
|
+
if (value.startsWith('[')) {
|
|
66
|
+
const arrayMatch = value.match(/\[(.*)\]/);
|
|
67
|
+
if (arrayMatch) {
|
|
68
|
+
const items = arrayMatch[1].split(',').map(item => {
|
|
69
|
+
const trimmedItem = item.trim();
|
|
70
|
+
// Remove quotes
|
|
71
|
+
if (trimmedItem.startsWith('"') && trimmedItem.endsWith('"')) {
|
|
72
|
+
return trimmedItem.slice(1, -1);
|
|
73
|
+
}
|
|
74
|
+
return trimmedItem;
|
|
75
|
+
});
|
|
76
|
+
result[currentSection][currentTable][key] = items;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Parse strings
|
|
80
|
+
else if (value.startsWith('"') && value.endsWith('"')) {
|
|
81
|
+
result[currentSection][currentTable][key] = value.slice(1, -1);
|
|
82
|
+
}
|
|
83
|
+
// Parse other values
|
|
84
|
+
else {
|
|
85
|
+
result[currentSection][currentTable][key] = value;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Simple TOML stringifier for MCP server configs
|
|
94
|
+
function stringifySimpleToml(obj) {
|
|
95
|
+
const lines = [];
|
|
96
|
+
|
|
97
|
+
for (const [section, tables] of Object.entries(obj)) {
|
|
98
|
+
if (typeof tables !== 'object' || tables === null) continue;
|
|
99
|
+
|
|
100
|
+
for (const [tableName, config] of Object.entries(tables)) {
|
|
101
|
+
if (typeof config !== 'object' || config === null) continue;
|
|
102
|
+
|
|
103
|
+
// Write table header [section.tableName]
|
|
104
|
+
lines.push(`[${section}.${tableName}]`);
|
|
105
|
+
|
|
106
|
+
// Write key-value pairs
|
|
107
|
+
for (const [key, value] of Object.entries(config)) {
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
// Format arrays
|
|
110
|
+
const arrayStr = value.map(v => `"${v}"`).join(', ');
|
|
111
|
+
lines.push(`${key} = [${arrayStr}]`);
|
|
112
|
+
} else if (typeof value === 'string') {
|
|
113
|
+
lines.push(`${key} = "${value}"`);
|
|
114
|
+
} else {
|
|
115
|
+
lines.push(`${key} = ${value}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Add blank line between tables
|
|
120
|
+
lines.push('');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return lines.join('\n');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function loadToml(filePath) {
|
|
128
|
+
if (!(await pathExists(filePath))) {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
134
|
+
return raw.trim() ? parseSimpleToml(raw) : {};
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const backupPath = `${filePath}.bak`;
|
|
137
|
+
await fs.copyFile(filePath, backupPath);
|
|
138
|
+
return {};
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function loadConfig(filePath) {
|
|
143
|
+
return isTomlFile(filePath) ? loadToml(filePath) : loadJson(filePath);
|
|
144
|
+
}
|
|
145
|
+
|
|
28
146
|
function mergeServers(existing, incoming, serverKey = "servers") {
|
|
29
147
|
const existingServers = existing[serverKey] && typeof existing[serverKey] === "object"
|
|
30
148
|
? existing[serverKey]
|
|
@@ -84,9 +202,14 @@ function removeServers(existing, removeNames, serverKey = "servers") {
|
|
|
84
202
|
|
|
85
203
|
async function installMcpConfig(configPath, servers, serverKey = "servers") {
|
|
86
204
|
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
87
|
-
const existing = await
|
|
205
|
+
const existing = await loadConfig(configPath);
|
|
88
206
|
const updated = mergeServers(existing, servers, serverKey);
|
|
89
|
-
|
|
207
|
+
|
|
208
|
+
if (isTomlFile(configPath)) {
|
|
209
|
+
await fs.writeFile(configPath, stringifySimpleToml(updated), "utf8");
|
|
210
|
+
} else {
|
|
211
|
+
await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
|
|
212
|
+
}
|
|
90
213
|
}
|
|
91
214
|
|
|
92
215
|
async function removeMcpConfig(configPath, serverNames, serverKey = "servers") {
|
|
@@ -94,14 +217,18 @@ async function removeMcpConfig(configPath, serverNames, serverKey = "servers") {
|
|
|
94
217
|
return { removed: 0, changed: false };
|
|
95
218
|
}
|
|
96
219
|
|
|
97
|
-
const existing = await
|
|
220
|
+
const existing = await loadConfig(configPath);
|
|
98
221
|
const { updated, removed } = removeServers(existing, serverNames, serverKey);
|
|
99
222
|
|
|
100
223
|
if (removed === 0) {
|
|
101
224
|
return { removed: 0, changed: false };
|
|
102
225
|
}
|
|
103
226
|
|
|
104
|
-
|
|
227
|
+
if (isTomlFile(configPath)) {
|
|
228
|
+
await fs.writeFile(configPath, stringifySimpleToml(updated), "utf8");
|
|
229
|
+
} else {
|
|
230
|
+
await fs.writeFile(configPath, `${JSON.stringify(updated, null, 2)}\n`, "utf8");
|
|
231
|
+
}
|
|
105
232
|
return { removed, changed: true };
|
|
106
233
|
}
|
|
107
234
|
|